├── .dockerignore ├── .env.template ├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.md │ └── feature-request.md └── workflows │ └── python-package.yml ├── .gitignore ├── DEPLOYMENT.md ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── VERSION.py ├── VERSION_PYTHON.py ├── compose.build.yaml ├── compose.dev.yaml ├── compose.yaml ├── docker ├── Dockerfile └── docker-entrypoint.sh ├── docs-mkdocs ├── data.md ├── exceptions.md ├── extra.css ├── extra.js ├── index.md ├── logger.md ├── models.md ├── query.md ├── quickstart.md ├── readme.md ├── scripts │ └── update_docs_for_github_pages.py ├── test.md ├── utils.md └── yfpy-logo.svg ├── docs ├── 404.html ├── CNAME ├── assets │ └── _mkdocstrings.css ├── css │ ├── fonts │ │ ├── Roboto-Slab-Bold.woff │ │ ├── Roboto-Slab-Bold.woff2 │ │ ├── Roboto-Slab-Regular.woff │ │ ├── Roboto-Slab-Regular.woff2 │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ ├── fontawesome-webfont.woff2 │ │ ├── lato-bold-italic.woff │ │ ├── lato-bold-italic.woff2 │ │ ├── lato-bold.woff │ │ ├── lato-bold.woff2 │ │ ├── lato-normal-italic.woff │ │ ├── lato-normal-italic.woff2 │ │ ├── lato-normal.woff │ │ └── lato-normal.woff2 │ ├── theme.css │ └── theme_extra.css ├── data │ └── index.html ├── exceptions │ └── index.html ├── extra.css ├── extra.js ├── img │ └── favicon.ico ├── index.html ├── js │ ├── html5shiv.min.js │ ├── jquery-3.6.0.min.js │ ├── theme.js │ └── theme_extra.js ├── logger │ └── index.html ├── models │ └── index.html ├── objects.inv ├── query │ └── index.html ├── quickstart │ └── index.html ├── readme │ └── index.html ├── scripts │ └── update_docs_for_github_pages.py ├── search.html ├── search │ ├── lunr.js │ ├── main.js │ ├── search_index.json │ └── worker.js ├── sitemap.xml ├── sitemap.xml.gz ├── test │ └── index.html ├── utils │ └── index.html └── yfpy-logo.svg ├── mkdocs.yml ├── quickstart └── quickstart.py ├── requirements-dev.txt ├── requirements.txt ├── resources ├── documentation │ └── CNAME └── images │ ├── donate-bitcoin.png │ ├── donate-ethereum.png │ └── donate-paypal.png ├── setup.cfg ├── setup.py ├── test ├── __init__.py ├── conftest.py └── integration │ ├── __init__.py │ ├── conftest.py │ ├── test_api_game_data.py │ ├── test_api_league_data.py │ ├── test_api_player_data.py │ ├── test_api_team_data.py │ └── test_api_user_data.py └── yfpy ├── __init__.py ├── data.py ├── exceptions.py ├── logger.py ├── models.py ├── query.py └── utils.py /.dockerignore: -------------------------------------------------------------------------------- 1 | # macOS 2 | **.DS_Store 3 | 4 | # JetBrains 5 | .idea/ 6 | 7 | # Python 8 | **__pycache__ 9 | **.pytest_cache 10 | 11 | # environment 12 | **.env* 13 | !**.env.template* 14 | .venv 15 | 16 | # auth secrets 17 | private.json 18 | !**private.template.json 19 | **token*.json 20 | !**token.template.json 21 | 22 | # Docker 23 | compose*.yaml 24 | .dockerignore 25 | docker/Dockerfile 26 | -------------------------------------------------------------------------------- /.env.template: -------------------------------------------------------------------------------- 1 | # development variable to expose any fields returned from the Yahoo Fantasy Sports API not currently supported by YFPY 2 | CHECK_FOR_MISSING_YAHOO_DATA=false 3 | 4 | # Yahoo access token fields for reauthentication 5 | YAHOO_CONSUMER_KEY= 6 | YAHOO_CONSUMER_SECRET= 7 | YAHOO_ACCESS_TOKEN= 8 | YAHOO_GUID= 9 | YAHOO_REFRESH_TOKEN= 10 | YAHOO_TOKEN_TIME= 11 | YAHOO_TOKEN_TYPE= 12 | 13 | # Yahoo access token JSON string containing all required access token fields 14 | YAHOO_ACCESS_TOKEN_JSON= 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: 'Report any bugs, errors, problems, etc. found in YFPY. ' 4 | title: "[Bug] Short Description of Erroneous/Problematic Behavior" 5 | labels: bug 6 | assignees: uberfastman 7 | 8 | --- 9 | 10 | ## Description of Issue 11 | Please put a longer description of the bug/issue/problem you are encountering here, as well as anything else you think might be relevant or helpful to reproduce the issue. 12 | 13 | ## Additional Information as Applicable 14 | 15 | #### Yahoo Fantasy Sports League ID 16 | XXXXXX... 17 | 18 | #### Yahoo Fantasy Sports League Privacy 19 | Public/Private 20 | 21 | #### Operating System/Environment 22 | macOS/Windows/Linux/Docker/etc. 23 | 24 | #### Other 25 | Any other setup/environment/configuration information you might deem potentially relevant. 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest a new idea/feature for YFPY. 4 | title: "[Feature Request] Short Description of New Idea/Feature" 5 | labels: feature 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Description 11 | Please describe what new idea/feature would like to see added to the YFPY package. 12 | 13 | ## Use case 14 | Please elaborate on how your idea/feature request will provide additional value/functionality. 15 | -------------------------------------------------------------------------------- /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: CI Build 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | pull_request: 11 | branches: 12 | - main 13 | 14 | jobs: 15 | build: 16 | 17 | runs-on: ubuntu-latest 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | python-version: ["3.10", "3.11", "3.12"] 22 | 23 | steps: 24 | - uses: actions/checkout@v4 25 | - name: Set up Python ${{ matrix.python-version }} 26 | uses: actions/setup-python@v5 27 | with: 28 | python-version: ${{ matrix.python-version }} 29 | - name: Install dependencies 30 | run: | 31 | python -m pip install --upgrade pip 32 | python -m pip install flake8 bandit pytest 33 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 34 | - name: Lint with flake8 35 | run: | 36 | # stop the build if there are Python syntax errors 37 | flake8 . --count --show-source --statistics 38 | # exit-zero treats all errors as warnings 39 | flake8 . --count --exit-zero --statistics 40 | - name: Security lint with bandit 41 | run: | 42 | # stop the build if there are security warnings 43 | bandit -r yfpy/ 44 | # - name: Test with pytest 45 | # run: | 46 | # pytest 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # python 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # build 6 | .Python 7 | build/ 8 | dist/ 9 | *.egg-info/ 10 | 11 | # miscelleanous 12 | .DS_Store 13 | 14 | # development 15 | .idea/* 16 | .vscode 17 | 18 | # Testing 19 | .pytest_cache/ 20 | test_output/ 21 | 22 | # Environment 23 | *.env* 24 | !.env.template 25 | *.venv 26 | .python-version 27 | 28 | # Credentials 29 | *private*.json 30 | !private.template.json 31 | *token*.json 32 | -------------------------------------------------------------------------------- /DEPLOYMENT.md: -------------------------------------------------------------------------------- 1 | # YFPY Deployment 2 | 3 | 1. *(Optional)* Clear virtual machine of old requirements: 4 | 5 | ```shell 6 | pip uninstall -y -r <(pip freeze) 7 | ``` 8 | 9 | 2. *(Optional)* Check `requirements.txt` and `requirement-dev.txt` for latest dependency versions. 10 | 11 | 3. *(Optional)* Update virtual machine with the latest dependencies: 12 | 13 | ```shell 14 | pip install -r requirements.txt -r requirements-dev.txt 15 | ``` 16 | 17 | 4. Lint code with `flake8`: 18 | 19 | ```shell 20 | flake8 . --count --show-source --statistics 21 | ``` 22 | 23 | 5. Check code security with `bandit`: 24 | 25 | ```shell 26 | bandit -r yfpy/ 27 | ``` 28 | 29 | 6. Run *all* `pytest` tests (see following commands for running subsets of tests): 30 | 31 | ```shell 32 | python -m pytest 33 | ``` 34 | 35 | 7. Run *all* `pytest` tests *verbosely*: 36 | 37 | ```shell 38 | python -m pytest -v -s 39 | ``` 40 | 41 | 8. Run `pytest` integration tests: 42 | 43 | ```shell 44 | python -m pytest -v -s -m integration 45 | ``` 46 | 47 | 9. *(Optional)* Run all tests from `pytest` file: 48 | 49 | ```shell 50 | python -m pytest -v -s -m integration test/integration/test_api_game_data.py 51 | ``` 52 | 53 | 10. *(Optional)* Run *specific* test from `pytest` file: 54 | 55 | ```shell 56 | python -m pytest -v -s -m integration test/integration/test_api_game_data.py -k test_get_game_key_by_season 57 | ``` 58 | 59 | 11. *(Optional)* Test Python support using [act](https://github.com/nektos/act) for GitHub Actions: 60 | 61 | ```shell 62 | act_amd -j build 63 | ``` 64 | 65 | ***Note***: If `act` is unable to locate Docker, make sure that the required `/var/run/docker.sock` symlink exists. If it does not, you can fix it by running: 66 | 67 | ```shell 68 | sudo ln -s "$HOME/.docker/run/docker.sock" /var/run/docker.sock` 69 | ``` 70 | 71 | 12. *(Optional)* Build the PyPI package independent of deployment: 72 | 73 | ```shell 74 | make build 75 | ``` 76 | 77 | 13. *(Optional)* Test packages for PyPI deployment: 78 | 79 | ```shell 80 | make verify_build 81 | ``` 82 | 83 | 14. *(Optional)* Check MkDocs documentation by serving it at [http://localhost:8000/](http://localhost:8000/) locally: 84 | 85 | ```shell 86 | make test_docs 87 | ``` 88 | 89 | 15. *(Optional)* Build the PyPI package and MkDocs documentation independent of deployment: 90 | 91 | ```shell 92 | make docs 93 | ``` 94 | 95 | ***Note***: Running `make test_docs` from the previous step recreates the documentation without building the PyPI package with `setup.py`. 96 | 97 | 16. Create a git commit: 98 | 99 | ```shell 100 | git add . 101 | git commit -m 'commit message' 102 | ``` 103 | 104 | 17. Update the git tag with the new version: 105 | 106 | `git tag -a [tag_name/version] -m [message]` 107 | 108 | ```shell 109 | git tag -a v1.0.0 -m 'release message' 110 | git push origin --tags 111 | ``` 112 | 113 | 18. Install `twine` (if not already installed): 114 | 115 | ```shell 116 | pip install twine 117 | ``` 118 | 119 | 19. *(Optional)* Test deployment by building the PyPI packages, recreating the documentation, and deploying to Test PyPI: 120 | 121 | ```shell 122 | make test_deploy 123 | ``` 124 | 125 | 20. Deploy YFPY by building the PyPI packages, recreating the documentation, and deploying to PyPI: 126 | 127 | ```shell 128 | make deploy 129 | ``` 130 | 131 | 21. Build Docker container: 132 | ```shell 133 | docker compose -f compose.yaml -f compose.build.yaml build 134 | ``` 135 | 136 | 22. *(If needed)* Authenticate with GitHub Personal Access Token (PAT): 137 | ```shell 138 | jq -r .github_personal_access_token.value private-github.json | docker login ghcr.io -u uberfastman --password-stdin 139 | ``` 140 | 141 | 23. Deploy the newly-built Docker image with respective major, minor, and patch version numbers to the GitHub Container Registry: 142 | ```shell 143 | docker push ghcr.io/uberfastman/yfpy:X.X.X 144 | ``` 145 | 146 | 24. Create a second git commit with updated version number and documentation: 147 | 148 | ```shell 149 | git add . 150 | git commit -m 'update version number and docs' 151 | ``` 152 | 153 | 25. Update YFPY GitHub repository: 154 | 155 | ```shell 156 | git push 157 | ``` 158 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # Include the README 2 | include README.md 3 | 4 | # Include the license file 5 | include LICENSE 6 | 7 | # Include dependency lists 8 | include requirements.txt 9 | include requirements-dev.txt 10 | 11 | # Include Yahoo Fantasy Sports REST API authentication resources 12 | include .env.template 13 | 14 | # Include YFPY Docker resources 15 | include compose.yaml 16 | include compose.dev.yaml 17 | include compose.build.yaml 18 | 19 | # Include quickstart resources 20 | include quickstart/quickstart.py 21 | 22 | # Include test code and resources 23 | include test/conftest.py 24 | include test/integration/conftest.py 25 | include test/integration/test_api_game_data.py 26 | include test/integration/test_api_league_data.py 27 | include test/integration/test_api_player_data.py 28 | include test/integration/test_api_team_data.py 29 | include test/integration/test_api_user_data.py 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # You can set these variables from the command line and also from the environment. 2 | DOCS_OPTS ?= 3 | DOCS_BUILD ?= mkdocs 4 | 5 | # Determine this makefile's path. Be sure to place this BEFORE `include` directives, if any. 6 | THIS_FILE := $(lastword $(MAKEFILE_LIST)) 7 | 8 | # Put it first so that "make" without argument is like "make help". 9 | help: 10 | @$(DOCS_BUILD) -h $(DOCS_OPTS) $(O) 11 | 12 | .PHONY: build verify_build test_docs docs pages test_deploy deploy help Makefile 13 | 14 | build: ## Build PyPI packages for distribution. 15 | cd ${HOME}/Projects/personal/yfpy; python setup.py sdist bdist_wheel 16 | 17 | verify_build: ## Check PyPI packages for issues. 18 | twine check dist/* 19 | 20 | test_docs: ## Extract YFPY project version from VERSION.py and serve MkDocs documentation locally for testing. 21 | export YFPY_VERSION=$$(python -c "from VERSION import __version__; print(__version__)") && mkdocs serve 22 | 23 | docs: build ## Extract YFPY project version from VERSION.py and build MkDocs documentation for distribution. 24 | export YFPY_VERSION=$$(python -c "from VERSION import __version__; print(__version__)") && mkdocs build 25 | 26 | pages: docs ## Prepare documentation in /docs directory for GitHub Pages. 27 | python docs-mkdocs/scripts/update_docs_for_github_pages.py 28 | 29 | test_deploy: pages ## Deploy PyPI packages to Test PyPI. 30 | twine upload -r testpypi dist/* 31 | 32 | deploy: pages ## Deploy PyPI packages to PyPI. 33 | twine upload dist/* 34 | -------------------------------------------------------------------------------- /VERSION.py: -------------------------------------------------------------------------------- 1 | # DO NOT EDIT - VERSIONING CONTROLLED BY GIT TAGS 2 | __version__ = "v16.0.3" 3 | -------------------------------------------------------------------------------- /VERSION_PYTHON.py: -------------------------------------------------------------------------------- 1 | # minimum supported Python version 2 | __version_minimum_python__ = "3.10" 3 | 4 | # minimum supported Python version 5 | __version_maximum_python__ = "3.12" 6 | -------------------------------------------------------------------------------- /compose.build.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | package: 3 | build: 4 | # DO NOT EDIT PYTHON VERSION BUILD ARGUMENTS - VERSIONING CONTROLLED BY GIT TAGS 5 | args: 6 | - PYTHON_VERSION_MAJOR=3 7 | - PYTHON_VERSION_MINOR=12 8 | - PYTHON_VERSION_PATCH=0 9 | - BUILD_PLATFORM=linux/amd64 10 | context: . 11 | dockerfile: docker/Dockerfile 12 | -------------------------------------------------------------------------------- /compose.dev.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | package: 3 | volumes: 4 | - .:/opt/yfpy 5 | -------------------------------------------------------------------------------- /compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | package: 3 | image: ghcr.io/uberfastman/yfpy:16.0.3 # DO NOT EDIT IMAGE - VERSIONING CONTROLLED BY GIT TAGS 4 | platform: linux/amd64 5 | environment: 6 | - RUNTIME_ENVIRONMENT=docker 7 | env_file: 8 | - ./.env 9 | volumes: 10 | - /etc/localtime:/etc/localtime # sync container timezone with host 11 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG PYTHON_VERSION_MAJOR=$PYTHON_VERSION_MAJOR 2 | ARG PYTHON_VERSION_MINOR=$PYTHON_VERSION_MINOR 3 | ARG PYTHON_VERSION_PATCH=$PYTHON_VERSION_PATCH 4 | 5 | ARG BUILD_PLATFORM=$BUILD_PLATFORM 6 | 7 | # set base image 8 | FROM --platform=${BUILD_PLATFORM} python:${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}.${PYTHON_VERSION_PATCH}-slim 9 | 10 | LABEL "org.opencontainers.image.source"="https://github.com/uberfastman/yfpy" 11 | 12 | # update package index list 13 | RUN apt-get update -y && \ 14 | apt-get upgrade -y && \ 15 | apt-get install -y git 16 | 17 | # set the working directory in the container 18 | WORKDIR /opt/yfpy 19 | 20 | # set python environment variables 21 | ENV PYTHONDONTWRITEBYTECODE 1 22 | ENV PYTHONUNBUFFERED 1 23 | 24 | ## copy the project root contents to the working directory 25 | COPY .. . 26 | 27 | # install dependencies 28 | RUN pip install --upgrade pip && \ 29 | pip install -r requirements.txt 30 | 31 | RUN chmod +x ./docker/docker-entrypoint.sh 32 | 33 | # specify docker as package runtime environment 34 | ENV RUNTIME_ENVIRONMENT docker 35 | 36 | ENTRYPOINT ["./docker/docker-entrypoint.sh"] 37 | 38 | # command to run on container start 39 | CMD tail -f /dev/null 40 | -------------------------------------------------------------------------------- /docker/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # startup message 4 | /bin/echo "YFPY is ready!" 5 | 6 | # execute CMD from Dockerfile 7 | exec "$@" 8 | -------------------------------------------------------------------------------- /docs-mkdocs/data.md: -------------------------------------------------------------------------------- 1 | 2 | # `Data` 3 | 4 | ::: yfpy.data 5 | show_root_heading: true 6 | show_source: true 7 | -------------------------------------------------------------------------------- /docs-mkdocs/exceptions.md: -------------------------------------------------------------------------------- 1 | 2 | # `Exceptions` 3 | 4 | ::: yfpy.exceptions 5 | show_root_heading: true 6 | show_source: true 7 | -------------------------------------------------------------------------------- /docs-mkdocs/extra.css: -------------------------------------------------------------------------------- 1 | 2 | /* CSS for readthedocs theme */ 3 | 4 | /* remove duplicate module nav item on the top of each module members list */ 5 | body > div.wy-grid-for-nav > nav > div > div.wy-menu.wy-menu-vertical > ul.current > li.toctree-l1.current > ul > li:nth-child(1) > a { 6 | display: none; 7 | } 8 | 9 | /* CSS for snippet version of README.md */ 10 | 11 | /* hide YFPY logo */ 12 | body > div.wy-grid-for-nav > section > div > div > div.document > div > p:nth-child(1) { 13 | display: none; 14 | } 15 | 16 | /* hide "read the docs here" section */ 17 | body > div.wy-grid-for-nav > section > div > div > div.document > div > p:nth-child(13) { 18 | display: none; 19 | } 20 | 21 | /* hide in-line TOC (title, table of contents, and horizontal line) */ 22 | #table-of-contents, 23 | body > div.wy-grid-for-nav > section > div > div > div.document > div > ul:nth-child(15), 24 | body > div.wy-grid-for-nav > section > div > div > div.document > div > hr:nth-child(16) { 25 | display: none; 26 | } 27 | -------------------------------------------------------------------------------- /docs-mkdocs/extra.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uberfastman/yfpy/095cc5e3cd88a56b1a337f3169001f35a901c6f9/docs-mkdocs/extra.js -------------------------------------------------------------------------------- /docs-mkdocs/index.md: -------------------------------------------------------------------------------- 1 | 2 | [](https://github.com/uberfastman/yfpy) 3 | 4 | # Welcome to YFPY! 5 | 6 | Please check out the [README](./readme.md) to get started! 7 | -------------------------------------------------------------------------------- /docs-mkdocs/logger.md: -------------------------------------------------------------------------------- 1 | 2 | # `Logging` 3 | 4 | ::: yfpy.logger 5 | show_root_heading: true 6 | show_source: true 7 | -------------------------------------------------------------------------------- /docs-mkdocs/models.md: -------------------------------------------------------------------------------- 1 | 2 | # `Models` 3 | 4 | ::: yfpy.models 5 | show_root_heading: true 6 | show_source: true 7 | -------------------------------------------------------------------------------- /docs-mkdocs/query.md: -------------------------------------------------------------------------------- 1 | 2 | # `Query` 3 | 4 | ::: yfpy.query 5 | show_root_heading: true 6 | show_source: true 7 | -------------------------------------------------------------------------------- /docs-mkdocs/quickstart.md: -------------------------------------------------------------------------------- 1 | ```python 2 | --8<-- "quickstart/quickstart.py" 3 | ``` 4 | -------------------------------------------------------------------------------- /docs-mkdocs/readme.md: -------------------------------------------------------------------------------- 1 | --8<-- "README.md" 2 | -------------------------------------------------------------------------------- /docs-mkdocs/scripts/update_docs_for_github_pages.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import shutil 3 | 4 | base_project_dir = Path(__file__).parent.parent.parent 5 | docs_dir = base_project_dir / "docs" 6 | 7 | print("Copying CNAME file to \"/docs\" for GitHub Pages...") 8 | 9 | cname_file_path = base_project_dir / "resources" / "documentation" / "CNAME" 10 | shutil.copyfile(cname_file_path, docs_dir / "CNAME") 11 | 12 | print("...successfully prepared docs for GitHub Pages.") 13 | -------------------------------------------------------------------------------- /docs-mkdocs/test.md: -------------------------------------------------------------------------------- 1 | 2 | # `PyTest` 3 | 4 | ::: test 5 | show_root_heading: true 6 | show_source: true 7 | -------------------------------------------------------------------------------- /docs-mkdocs/utils.md: -------------------------------------------------------------------------------- 1 | 2 | # `Utilities` 3 | 4 | ::: yfpy.utils 5 | show_root_heading: true 6 | show_source: true 7 | -------------------------------------------------------------------------------- /docs-mkdocs/yfpy-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 36 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 151 | -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | YFPY - Yahoo Fantasy Sports API Wrapper 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 80 | 81 |
82 | 87 |
88 |
89 |
    90 |
  • 91 |
  • 92 |
  • 93 |
94 |
95 |
96 |
97 |
98 | 99 | 100 |

404

101 | 102 |

Page not found

103 | 104 | 105 |
106 |
116 | 117 |
118 |
119 | 120 |
121 | 122 |
123 | 124 |
125 | 126 | 127 | 128 | GitHub 129 | 130 | 131 | 132 | 133 | 134 |
135 | 136 | 137 | 138 | 139 | 140 | 141 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | yfpy.uberfastman.com -------------------------------------------------------------------------------- /docs/assets/_mkdocstrings.css: -------------------------------------------------------------------------------- 1 | 2 | /* Avoid breaking parameters name, etc. in table cells. */ 3 | .doc-contents td code { 4 | word-break: normal !important; 5 | } 6 | 7 | /* No line break before first paragraph of descriptions. */ 8 | .doc-md-description, 9 | .doc-md-description>p:first-child { 10 | display: inline; 11 | } 12 | 13 | /* Avoid breaking code headings. */ 14 | .doc-heading code { 15 | white-space: normal; 16 | } 17 | 18 | /* Improve rendering of parameters, returns and exceptions. */ 19 | .doc-contents .field-name { 20 | min-width: 100px; 21 | } 22 | 23 | /* Other curious-spacing fixes. */ 24 | .doc-contents .field-name, 25 | .doc-contents .field-body { 26 | border: none !important; 27 | padding: 0 !important; 28 | } 29 | 30 | .doc-contents p { 31 | margin: 1em 0 1em; 32 | } 33 | 34 | .doc-contents .field-list { 35 | margin: 0 !important; 36 | } 37 | 38 | .doc-contents pre { 39 | padding: 0 !important; 40 | } 41 | 42 | .doc-contents .wy-table-responsive { 43 | margin-bottom: 0 !important; 44 | } 45 | 46 | .doc-contents td.code { 47 | padding: 0 !important; 48 | } 49 | 50 | .doc-contents td.linenos { 51 | padding: 0 8px !important; 52 | } 53 | 54 | .doc-children, 55 | footer { 56 | margin-top: 20px; 57 | } -------------------------------------------------------------------------------- /docs/css/fonts/Roboto-Slab-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uberfastman/yfpy/095cc5e3cd88a56b1a337f3169001f35a901c6f9/docs/css/fonts/Roboto-Slab-Bold.woff -------------------------------------------------------------------------------- /docs/css/fonts/Roboto-Slab-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uberfastman/yfpy/095cc5e3cd88a56b1a337f3169001f35a901c6f9/docs/css/fonts/Roboto-Slab-Bold.woff2 -------------------------------------------------------------------------------- /docs/css/fonts/Roboto-Slab-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uberfastman/yfpy/095cc5e3cd88a56b1a337f3169001f35a901c6f9/docs/css/fonts/Roboto-Slab-Regular.woff -------------------------------------------------------------------------------- /docs/css/fonts/Roboto-Slab-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uberfastman/yfpy/095cc5e3cd88a56b1a337f3169001f35a901c6f9/docs/css/fonts/Roboto-Slab-Regular.woff2 -------------------------------------------------------------------------------- /docs/css/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uberfastman/yfpy/095cc5e3cd88a56b1a337f3169001f35a901c6f9/docs/css/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/css/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uberfastman/yfpy/095cc5e3cd88a56b1a337f3169001f35a901c6f9/docs/css/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/css/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uberfastman/yfpy/095cc5e3cd88a56b1a337f3169001f35a901c6f9/docs/css/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/css/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uberfastman/yfpy/095cc5e3cd88a56b1a337f3169001f35a901c6f9/docs/css/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /docs/css/fonts/lato-bold-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uberfastman/yfpy/095cc5e3cd88a56b1a337f3169001f35a901c6f9/docs/css/fonts/lato-bold-italic.woff -------------------------------------------------------------------------------- /docs/css/fonts/lato-bold-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uberfastman/yfpy/095cc5e3cd88a56b1a337f3169001f35a901c6f9/docs/css/fonts/lato-bold-italic.woff2 -------------------------------------------------------------------------------- /docs/css/fonts/lato-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uberfastman/yfpy/095cc5e3cd88a56b1a337f3169001f35a901c6f9/docs/css/fonts/lato-bold.woff -------------------------------------------------------------------------------- /docs/css/fonts/lato-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uberfastman/yfpy/095cc5e3cd88a56b1a337f3169001f35a901c6f9/docs/css/fonts/lato-bold.woff2 -------------------------------------------------------------------------------- /docs/css/fonts/lato-normal-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uberfastman/yfpy/095cc5e3cd88a56b1a337f3169001f35a901c6f9/docs/css/fonts/lato-normal-italic.woff -------------------------------------------------------------------------------- /docs/css/fonts/lato-normal-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uberfastman/yfpy/095cc5e3cd88a56b1a337f3169001f35a901c6f9/docs/css/fonts/lato-normal-italic.woff2 -------------------------------------------------------------------------------- /docs/css/fonts/lato-normal.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uberfastman/yfpy/095cc5e3cd88a56b1a337f3169001f35a901c6f9/docs/css/fonts/lato-normal.woff -------------------------------------------------------------------------------- /docs/css/fonts/lato-normal.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uberfastman/yfpy/095cc5e3cd88a56b1a337f3169001f35a901c6f9/docs/css/fonts/lato-normal.woff2 -------------------------------------------------------------------------------- /docs/css/theme_extra.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Wrap inline code samples otherwise they shoot of the side and 3 | * can't be read at all. 4 | * 5 | * https://github.com/mkdocs/mkdocs/issues/313 6 | * https://github.com/mkdocs/mkdocs/issues/233 7 | * https://github.com/mkdocs/mkdocs/issues/834 8 | */ 9 | .rst-content code { 10 | white-space: pre-wrap; 11 | word-wrap: break-word; 12 | padding: 2px 5px; 13 | } 14 | 15 | /** 16 | * Make code blocks display as blocks and give them the appropriate 17 | * font size and padding. 18 | * 19 | * https://github.com/mkdocs/mkdocs/issues/855 20 | * https://github.com/mkdocs/mkdocs/issues/834 21 | * https://github.com/mkdocs/mkdocs/issues/233 22 | */ 23 | .rst-content pre code { 24 | white-space: pre; 25 | word-wrap: normal; 26 | display: block; 27 | padding: 12px; 28 | font-size: 12px; 29 | } 30 | 31 | /** 32 | * Fix code colors 33 | * 34 | * https://github.com/mkdocs/mkdocs/issues/2027 35 | */ 36 | .rst-content code { 37 | color: #E74C3C; 38 | } 39 | 40 | .rst-content pre code { 41 | color: #000; 42 | background: #f8f8f8; 43 | } 44 | 45 | /* 46 | * Fix link colors when the link text is inline code. 47 | * 48 | * https://github.com/mkdocs/mkdocs/issues/718 49 | */ 50 | a code { 51 | color: #2980B9; 52 | } 53 | a:hover code { 54 | color: #3091d1; 55 | } 56 | a:visited code { 57 | color: #9B59B6; 58 | } 59 | 60 | /* 61 | * The CSS classes from highlight.js seem to clash with the 62 | * ReadTheDocs theme causing some code to be incorrectly made 63 | * bold and italic. 64 | * 65 | * https://github.com/mkdocs/mkdocs/issues/411 66 | */ 67 | pre .cs, pre .c { 68 | font-weight: inherit; 69 | font-style: inherit; 70 | } 71 | 72 | /* 73 | * Fix some issues with the theme and non-highlighted code 74 | * samples. Without and highlighting styles attached the 75 | * formatting is broken. 76 | * 77 | * https://github.com/mkdocs/mkdocs/issues/319 78 | */ 79 | .rst-content .no-highlight { 80 | display: block; 81 | padding: 0.5em; 82 | color: #333; 83 | } 84 | 85 | 86 | /* 87 | * Additions specific to the search functionality provided by MkDocs 88 | */ 89 | 90 | .search-results { 91 | margin-top: 23px; 92 | } 93 | 94 | .search-results article { 95 | border-top: 1px solid #E1E4E5; 96 | padding-top: 24px; 97 | } 98 | 99 | .search-results article:first-child { 100 | border-top: none; 101 | } 102 | 103 | form .search-query { 104 | width: 100%; 105 | border-radius: 50px; 106 | padding: 6px 12px; 107 | border-color: #D1D4D5; 108 | } 109 | 110 | /* 111 | * Improve inline code blocks within admonitions. 112 | * 113 | * https://github.com/mkdocs/mkdocs/issues/656 114 | */ 115 | .rst-content .admonition code { 116 | color: #404040; 117 | border: 1px solid #c7c9cb; 118 | border: 1px solid rgba(0, 0, 0, 0.2); 119 | background: #f8fbfd; 120 | background: rgba(255, 255, 255, 0.7); 121 | } 122 | 123 | /* 124 | * Account for wide tables which go off the side. 125 | * Override borders to avoid weirdness on narrow tables. 126 | * 127 | * https://github.com/mkdocs/mkdocs/issues/834 128 | * https://github.com/mkdocs/mkdocs/pull/1034 129 | */ 130 | .rst-content .section .docutils { 131 | width: 100%; 132 | overflow: auto; 133 | display: block; 134 | border: none; 135 | } 136 | 137 | td, th { 138 | border: 1px solid #e1e4e5 !important; 139 | border-collapse: collapse; 140 | } 141 | 142 | /* 143 | * Without the following amendments, the navigation in the theme will be 144 | * slightly cut off. This is due to the fact that the .wy-nav-side has a 145 | * padding-bottom of 2em, which must not necessarily align with the font-size of 146 | * 90 % on the .rst-current-version container, combined with the padding of 12px 147 | * above and below. These amendments fix this in two steps: First, make sure the 148 | * .rst-current-version container has a fixed height of 40px, achieved using 149 | * line-height, and then applying a padding-bottom of 40px to this container. In 150 | * a second step, the items within that container are re-aligned using flexbox. 151 | * 152 | * https://github.com/mkdocs/mkdocs/issues/2012 153 | */ 154 | .wy-nav-side { 155 | padding-bottom: 40px; 156 | } 157 | 158 | /* For section-index only */ 159 | .wy-menu-vertical .current-section p { 160 | background-color: #e3e3e3; 161 | color: #404040; 162 | } 163 | 164 | /* 165 | * The second step of above amendment: Here we make sure the items are aligned 166 | * correctly within the .rst-current-version container. Using flexbox, we 167 | * achieve it in such a way that it will look like the following: 168 | * 169 | * [No repo_name] 170 | * Next >> // On the first page 171 | * << Previous Next >> // On all subsequent pages 172 | * 173 | * [With repo_name] 174 | * Next >> // On the first page 175 | * << Previous Next >> // On all subsequent pages 176 | * 177 | * https://github.com/mkdocs/mkdocs/issues/2012 178 | */ 179 | .rst-versions .rst-current-version { 180 | padding: 0 12px; 181 | display: flex; 182 | font-size: initial; 183 | justify-content: space-between; 184 | align-items: center; 185 | line-height: 40px; 186 | } 187 | 188 | /* 189 | * Please note that this amendment also involves removing certain inline-styles 190 | * from the file ./mkdocs/themes/readthedocs/versions.html. 191 | * 192 | * https://github.com/mkdocs/mkdocs/issues/2012 193 | */ 194 | .rst-current-version span { 195 | flex: 1; 196 | text-align: center; 197 | } 198 | -------------------------------------------------------------------------------- /docs/extra.css: -------------------------------------------------------------------------------- 1 | 2 | /* CSS for readthedocs theme */ 3 | 4 | /* remove duplicate module nav item on the top of each module members list */ 5 | body > div.wy-grid-for-nav > nav > div > div.wy-menu.wy-menu-vertical > ul.current > li.toctree-l1.current > ul > li:nth-child(1) > a { 6 | display: none; 7 | } 8 | 9 | /* CSS for snippet version of README.md */ 10 | 11 | /* hide YFPY logo */ 12 | body > div.wy-grid-for-nav > section > div > div > div.document > div > p:nth-child(1) { 13 | display: none; 14 | } 15 | 16 | /* hide "read the docs here" section */ 17 | body > div.wy-grid-for-nav > section > div > div > div.document > div > p:nth-child(13) { 18 | display: none; 19 | } 20 | 21 | /* hide in-line TOC (title, table of contents, and horizontal line) */ 22 | #table-of-contents, 23 | body > div.wy-grid-for-nav > section > div > div > div.document > div > ul:nth-child(15), 24 | body > div.wy-grid-for-nav > section > div > div > div.document > div > hr:nth-child(16) { 25 | display: none; 26 | } 27 | -------------------------------------------------------------------------------- /docs/extra.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uberfastman/yfpy/095cc5e3cd88a56b1a337f3169001f35a901c6f9/docs/extra.js -------------------------------------------------------------------------------- /docs/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uberfastman/yfpy/095cc5e3cd88a56b1a337f3169001f35a901c6f9/docs/img/favicon.ico -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | YFPY - Yahoo Fantasy Sports API Wrapper 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
32 | 89 | 90 |
91 | 96 |
97 |
98 |
    99 |
  • 100 | 101 |
  • 102 |
  • 103 |
104 |
105 |
106 |
107 |
108 | 109 |

110 |

Welcome to YFPY!

111 |

Please check out the README to get started!

112 | 113 |
114 |
127 | 128 |
129 |
130 | 131 |
132 | 133 |
134 | 135 |
136 | 137 | 138 | 139 | GitHub 140 | 141 | 142 | 143 | 144 | Next » 145 | 146 | 147 |
148 | 149 | 150 | 151 | 152 | 153 | 154 | 159 | 160 | 161 | 162 | 163 | 167 | -------------------------------------------------------------------------------- /docs/js/html5shiv.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); 5 | -------------------------------------------------------------------------------- /docs/js/theme.js: -------------------------------------------------------------------------------- 1 | /* sphinx_rtd_theme version 1.0.0 | MIT license */ 2 | !function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("
"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t 2 | 3 | 4 | 5 | 6 | 7 | 8 | Logging - YFPY - Yahoo Fantasy Sports API Wrapper 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
32 | 93 | 94 |
95 | 100 |
101 |
102 |
    103 |
  • 104 | 105 | 106 |
  • 107 |
  • 108 |
109 |
110 |
111 |
112 |
113 | 114 |

Logging

115 | 116 | 117 |
118 | 119 | 120 | 121 | 122 |
123 | 124 |

YFPY module for configuring and formatting the custom logger.

125 | 126 | 127 | 128 |
129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 |
139 | 140 | 141 |

142 | get_logger 143 | 144 | 145 |

146 |
get_logger(name, level=INFO)
147 | 
148 | 149 |
150 | 151 |

Get custom YFPY logger object.

152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 184 | 185 | 186 |
Parameters: 163 |
    164 |
  • 165 | name 166 | (str) 167 | – 168 |
    169 |

    The module name for the logger.

    170 |
    171 |
  • 172 |
  • 173 | level 174 | (int, default: 175 | INFO 176 | ) 177 | – 178 |
    179 |

    The log level for the logger. Default level set to INFO.

    180 |
    181 |
  • 182 |
183 |
187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 207 | 208 | 209 |
Returns: 197 |
    198 |
  • 199 | Logger( Logger 200 | ) – 201 |
    202 |

    A Python Logger object with custom configuration and formatting.

    203 |
    204 |
  • 205 |
206 |
210 |
211 | Source code in yfpy/logger.py 212 |
10
213 | 11
214 | 12
215 | 13
216 | 14
217 | 15
218 | 16
219 | 17
220 | 18
221 | 19
222 | 20
223 | 21
224 | 22
225 | 23
226 | 24
227 | 25
228 | 26
229 | 27
230 | 28
231 | 29
232 | 30
233 | 31
234 | 32
235 | 33
236 | 34
237 | 35
238 | 36
239 | 37
240 | 38
241 | 39
def get_logger(name: str, level: int = INFO) -> Logger:
242 |     """Get custom YFPY logger object.
243 | 
244 |     Args:
245 |         name (str): The module name for the logger.
246 |         level (int): The log level for the logger. Default level set to INFO.
247 | 
248 |     Returns:
249 |         Logger: A Python Logger object with custom configuration and formatting.
250 | 
251 |     """
252 |     logger = getLogger(name)
253 |     if len(logger.handlers) > 0:
254 |         logger.handlers = []
255 |     if level:
256 |         logger.setLevel(level)
257 | 
258 |     sh = StreamHandler()
259 |     if level:
260 |         sh.setLevel(level)
261 | 
262 |     formatter = Formatter(
263 |         fmt="%(asctime)s.%(msecs)03d - %(levelname)s - %(filename)s - %(name)s:%(lineno)d - %(message)s",
264 |         datefmt="%Y-%m-%d %H:%M:%S"
265 |     )
266 |     sh.setFormatter(formatter)
267 | 
268 |     logger.addHandler(sh)
269 | 
270 |     return logger
271 | 
272 |
273 |
274 | 275 |
276 | 277 | 278 | 279 |
280 | 281 |
282 | 283 |
284 | 285 |
286 |
300 | 301 |
302 |
303 | 304 |
305 | 306 |
307 | 308 |
309 | 310 | 311 | 312 | GitHub 313 | 314 | 315 | 316 | « Previous 317 | 318 | 319 | Next » 320 | 321 | 322 |
323 | 324 | 325 | 326 | 327 | 328 | 329 | 334 | 335 | 336 | 337 | -------------------------------------------------------------------------------- /docs/objects.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uberfastman/yfpy/095cc5e3cd88a56b1a337f3169001f35a901c6f9/docs/objects.inv -------------------------------------------------------------------------------- /docs/scripts/update_docs_for_github_pages.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import shutil 3 | 4 | base_project_dir = Path(__file__).parent.parent.parent 5 | docs_dir = base_project_dir / "docs" 6 | 7 | print("Copying CNAME file to \"/docs\" for GitHub Pages...") 8 | 9 | cname_file_path = base_project_dir / "resources" / "documentation" / "CNAME" 10 | shutil.copyfile(cname_file_path, docs_dir / "CNAME") 11 | 12 | print("...successfully prepared docs for GitHub Pages.") 13 | -------------------------------------------------------------------------------- /docs/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | YFPY - Yahoo Fantasy Sports API Wrapper 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 80 | 81 |
82 | 87 |
88 |
89 |
    90 |
  • 91 |
  • 92 |
  • 93 |
94 |
95 |
96 |
97 |
98 | 99 | 100 |

Search Results

101 | 102 | 106 | 107 |
108 | Searching... 109 |
110 | 111 | 112 |
113 |
123 | 124 |
125 |
126 | 127 |
128 | 129 |
130 | 131 |
132 | 133 | 134 | 135 | GitHub 136 | 137 | 138 | 139 | 140 | 141 |
142 | 143 | 144 | 145 | 146 | 147 | 148 | 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /docs/search/main.js: -------------------------------------------------------------------------------- 1 | function getSearchTermFromLocation() { 2 | var sPageURL = window.location.search.substring(1); 3 | var sURLVariables = sPageURL.split('&'); 4 | for (var i = 0; i < sURLVariables.length; i++) { 5 | var sParameterName = sURLVariables[i].split('='); 6 | if (sParameterName[0] == 'q') { 7 | return decodeURIComponent(sParameterName[1].replace(/\+/g, '%20')); 8 | } 9 | } 10 | } 11 | 12 | function joinUrl (base, path) { 13 | if (path.substring(0, 1) === "/") { 14 | // path starts with `/`. Thus it is absolute. 15 | return path; 16 | } 17 | if (base.substring(base.length-1) === "/") { 18 | // base ends with `/` 19 | return base + path; 20 | } 21 | return base + "/" + path; 22 | } 23 | 24 | function escapeHtml (value) { 25 | return value.replace(/&/g, '&') 26 | .replace(/"/g, '"') 27 | .replace(//g, '>'); 29 | } 30 | 31 | function formatResult (location, title, summary) { 32 | return ''; 33 | } 34 | 35 | function displayResults (results) { 36 | var search_results = document.getElementById("mkdocs-search-results"); 37 | while (search_results.firstChild) { 38 | search_results.removeChild(search_results.firstChild); 39 | } 40 | if (results.length > 0){ 41 | for (var i=0; i < results.length; i++){ 42 | var result = results[i]; 43 | var html = formatResult(result.location, result.title, result.summary); 44 | search_results.insertAdjacentHTML('beforeend', html); 45 | } 46 | } else { 47 | var noResultsText = search_results.getAttribute('data-no-results-text'); 48 | if (!noResultsText) { 49 | noResultsText = "No results found"; 50 | } 51 | search_results.insertAdjacentHTML('beforeend', '

' + noResultsText + '

'); 52 | } 53 | } 54 | 55 | function doSearch () { 56 | var query = document.getElementById('mkdocs-search-query').value; 57 | if (query.length > min_search_length) { 58 | if (!window.Worker) { 59 | displayResults(search(query)); 60 | } else { 61 | searchWorker.postMessage({query: query}); 62 | } 63 | } else { 64 | // Clear results for short queries 65 | displayResults([]); 66 | } 67 | } 68 | 69 | function initSearch () { 70 | var search_input = document.getElementById('mkdocs-search-query'); 71 | if (search_input) { 72 | search_input.addEventListener("keyup", doSearch); 73 | } 74 | var term = getSearchTermFromLocation(); 75 | if (term) { 76 | search_input.value = term; 77 | doSearch(); 78 | } 79 | } 80 | 81 | function onWorkerMessage (e) { 82 | if (e.data.allowSearch) { 83 | initSearch(); 84 | } else if (e.data.results) { 85 | var results = e.data.results; 86 | displayResults(results); 87 | } else if (e.data.config) { 88 | min_search_length = e.data.config.min_search_length-1; 89 | } 90 | } 91 | 92 | if (!window.Worker) { 93 | console.log('Web Worker API not supported'); 94 | // load index in main thread 95 | $.getScript(joinUrl(base_url, "search/worker.js")).done(function () { 96 | console.log('Loaded worker'); 97 | init(); 98 | window.postMessage = function (msg) { 99 | onWorkerMessage({data: msg}); 100 | }; 101 | }).fail(function (jqxhr, settings, exception) { 102 | console.error('Could not load worker.js'); 103 | }); 104 | } else { 105 | // Wrap search in a web worker 106 | var searchWorker = new Worker(joinUrl(base_url, "search/worker.js")); 107 | searchWorker.postMessage({init: true}); 108 | searchWorker.onmessage = onWorkerMessage; 109 | } 110 | -------------------------------------------------------------------------------- /docs/search/worker.js: -------------------------------------------------------------------------------- 1 | var base_path = 'function' === typeof importScripts ? '.' : '/search/'; 2 | var allowSearch = false; 3 | var index; 4 | var documents = {}; 5 | var lang = ['en']; 6 | var data; 7 | 8 | function getScript(script, callback) { 9 | console.log('Loading script: ' + script); 10 | $.getScript(base_path + script).done(function () { 11 | callback(); 12 | }).fail(function (jqxhr, settings, exception) { 13 | console.log('Error: ' + exception); 14 | }); 15 | } 16 | 17 | function getScriptsInOrder(scripts, callback) { 18 | if (scripts.length === 0) { 19 | callback(); 20 | return; 21 | } 22 | getScript(scripts[0], function() { 23 | getScriptsInOrder(scripts.slice(1), callback); 24 | }); 25 | } 26 | 27 | function loadScripts(urls, callback) { 28 | if( 'function' === typeof importScripts ) { 29 | importScripts.apply(null, urls); 30 | callback(); 31 | } else { 32 | getScriptsInOrder(urls, callback); 33 | } 34 | } 35 | 36 | function onJSONLoaded () { 37 | data = JSON.parse(this.responseText); 38 | var scriptsToLoad = ['lunr.js']; 39 | if (data.config && data.config.lang && data.config.lang.length) { 40 | lang = data.config.lang; 41 | } 42 | if (lang.length > 1 || lang[0] !== "en") { 43 | scriptsToLoad.push('lunr.stemmer.support.js'); 44 | if (lang.length > 1) { 45 | scriptsToLoad.push('lunr.multi.js'); 46 | } 47 | if (lang.includes("ja") || lang.includes("jp")) { 48 | scriptsToLoad.push('tinyseg.js'); 49 | } 50 | for (var i=0; i < lang.length; i++) { 51 | if (lang[i] != 'en') { 52 | scriptsToLoad.push(['lunr', lang[i], 'js'].join('.')); 53 | } 54 | } 55 | } 56 | loadScripts(scriptsToLoad, onScriptsLoaded); 57 | } 58 | 59 | function onScriptsLoaded () { 60 | console.log('All search scripts loaded, building Lunr index...'); 61 | if (data.config && data.config.separator && data.config.separator.length) { 62 | lunr.tokenizer.separator = new RegExp(data.config.separator); 63 | } 64 | 65 | if (data.index) { 66 | index = lunr.Index.load(data.index); 67 | data.docs.forEach(function (doc) { 68 | documents[doc.location] = doc; 69 | }); 70 | console.log('Lunr pre-built index loaded, search ready'); 71 | } else { 72 | index = lunr(function () { 73 | if (lang.length === 1 && lang[0] !== "en" && lunr[lang[0]]) { 74 | this.use(lunr[lang[0]]); 75 | } else if (lang.length > 1) { 76 | this.use(lunr.multiLanguage.apply(null, lang)); // spread operator not supported in all browsers: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator#Browser_compatibility 77 | } 78 | this.field('title'); 79 | this.field('text'); 80 | this.ref('location'); 81 | 82 | for (var i=0; i < data.docs.length; i++) { 83 | var doc = data.docs[i]; 84 | this.add(doc); 85 | documents[doc.location] = doc; 86 | } 87 | }); 88 | console.log('Lunr index built, search ready'); 89 | } 90 | allowSearch = true; 91 | postMessage({config: data.config}); 92 | postMessage({allowSearch: allowSearch}); 93 | } 94 | 95 | function init () { 96 | var oReq = new XMLHttpRequest(); 97 | oReq.addEventListener("load", onJSONLoaded); 98 | var index_path = base_path + '/search_index.json'; 99 | if( 'function' === typeof importScripts ){ 100 | index_path = 'search_index.json'; 101 | } 102 | oReq.open("GET", index_path); 103 | oReq.send(); 104 | } 105 | 106 | function search (query) { 107 | if (!allowSearch) { 108 | console.error('Assets for search still loading'); 109 | return; 110 | } 111 | 112 | var resultDocuments = []; 113 | var results = index.search(query); 114 | for (var i=0; i < results.length; i++){ 115 | var result = results[i]; 116 | doc = documents[result.ref]; 117 | doc.summary = doc.text.substring(0, 200); 118 | resultDocuments.push(doc); 119 | } 120 | return resultDocuments; 121 | } 122 | 123 | if( 'function' === typeof importScripts ) { 124 | onmessage = function (e) { 125 | if (e.data.init) { 126 | init(); 127 | } else if (e.data.query) { 128 | postMessage({ results: search(e.data.query) }); 129 | } else { 130 | console.error("Worker - Unrecognized message: " + e); 131 | } 132 | }; 133 | } 134 | -------------------------------------------------------------------------------- /docs/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | https://yfpy.uberfastman.com/ 5 | 2024-09-23 6 | 7 | 8 | https://yfpy.uberfastman.com/data/ 9 | 2024-09-23 10 | 11 | 12 | https://yfpy.uberfastman.com/exceptions/ 13 | 2024-09-23 14 | 15 | 16 | https://yfpy.uberfastman.com/logger/ 17 | 2024-09-23 18 | 19 | 20 | https://yfpy.uberfastman.com/models/ 21 | 2024-09-23 22 | 23 | 24 | https://yfpy.uberfastman.com/query/ 25 | 2024-09-23 26 | 27 | 28 | https://yfpy.uberfastman.com/quickstart/ 29 | 2024-09-23 30 | 31 | 32 | https://yfpy.uberfastman.com/readme/ 33 | 2024-09-23 34 | 35 | 36 | https://yfpy.uberfastman.com/test/ 37 | 2024-09-23 38 | 39 | 40 | https://yfpy.uberfastman.com/utils/ 41 | 2024-09-23 42 | 43 | -------------------------------------------------------------------------------- /docs/sitemap.xml.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uberfastman/yfpy/095cc5e3cd88a56b1a337f3169001f35a901c6f9/docs/sitemap.xml.gz -------------------------------------------------------------------------------- /docs/yfpy-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 36 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 151 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | ### Site metadata ### 2 | 3 | site_name: YFPY - Yahoo Fantasy Sports API Wrapper 4 | site_description: Python API wrapper for the Yahoo Fantasy Sports public API 5 | site_url: https://yfpy.uberfastman.com 6 | site_author: "Wren J. R. (uberfastman)" 7 | 8 | repo_url: https://github.com/uberfastman/yfpy 9 | edit_uri: "" 10 | 11 | ### Build settings ### 12 | 13 | extra: 14 | version: !ENV [YFPY_VERSION, 'vX.X.X'] 15 | 16 | docs_dir: docs-mkdocs 17 | site_dir: docs 18 | 19 | theme: 20 | # name: mkdocs 21 | name: readthedocs 22 | color_mode: auto 23 | user_color_mode_toggle: true 24 | navigation_depth: 6 25 | logo: yfpy-logo.svg 26 | 27 | extra_css: 28 | - extra.css 29 | 30 | extra_javascript: 31 | - extra.js 32 | 33 | nav: 34 | - Welcome: index.md 35 | - YFPY - Yahoo Fantasy Sports API Wrapper: readme.md 36 | - Example Usage: 37 | - Quickstart: quickstart.md 38 | - Package: 39 | - Query: query.md 40 | - Data: data.md 41 | - Models: models.md 42 | - Extras: 43 | - Utilities: utils.md 44 | - Exceptions: exceptions.md 45 | - Logging: logger.md 46 | - Testing: 47 | - PyTest: test.md 48 | 49 | markdown_extensions: 50 | - toc: 51 | permalink: true 52 | - pymdownx.snippets: 53 | check_paths: true 54 | - sane_lists 55 | - codehilite 56 | 57 | plugins: 58 | - search 59 | - autorefs 60 | - mkdocstrings: 61 | enabled: !ENV [ENABLE_MKDOCSTRINGS, true] 62 | # custom_templates: templates 63 | default_handler: python 64 | handlers: 65 | python: 66 | options: 67 | show_source: true 68 | separate_signature: true 69 | show_submodules: true 70 | docstring_section_style: table 71 | members_order: source 72 | summary: true 73 | -------------------------------------------------------------------------------- /quickstart/quickstart.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """YFPY demo. 3 | 4 | """ 5 | __author__ = "Wren J. R. (uberfastman)" 6 | __email__ = "uberfastman@uberfastman.dev" 7 | 8 | import os 9 | import sys 10 | from logging import DEBUG 11 | from pathlib import Path 12 | 13 | project_dir = Path(__file__).parent.parent 14 | sys.path.insert(0, str(project_dir)) 15 | 16 | from yfpy import Data # noqa: E402 17 | from yfpy.logger import get_logger # noqa: E402 18 | from yfpy.query import YahooFantasySportsQuery # noqa: E402 19 | 20 | """ 21 | Example public Yahoo league URL: "https://archive.fantasysports.yahoo.com/nfl/2014/729259" 22 | 23 | Example vars using public Yahoo leagues still require auth through a personal Yahoo account: see README.md 24 | """ 25 | 26 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 27 | # ENVIRONMENT SETUP # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 28 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 29 | 30 | # set target directory for data output 31 | data_dir = Path(__file__).parent / "output" 32 | 33 | # create YFPY Data instance for saving/loading data 34 | data = Data(data_dir) 35 | 36 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 37 | # VARIABLE SETUP # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 38 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 39 | 40 | 41 | # set desired season year 42 | def get_season(): 43 | # season = 2012 44 | # season = 2013 45 | # season = 2014 46 | # season = 2015 47 | # season = 2016 48 | # season = 2017 49 | # season = 2018 50 | # season = 2019 51 | # season = 2020 52 | # season = 2021 53 | # season = 2022 54 | # season = 2023 55 | season = 2024 56 | return season 57 | 58 | 59 | test_season = get_season() 60 | 61 | 62 | # set desired week 63 | def get_chosen_week(): 64 | chosen_week = 1 65 | return chosen_week 66 | 67 | 68 | test_chosen_week = get_chosen_week() 69 | 70 | 71 | # set desired date 72 | def get_chosen_date(): 73 | # HOCKEY 74 | # chosen_date = "2013-04-15" # NHL - 2013 (for 2012 season) 75 | chosen_date = "2021-10-25" # NHL - 2021 76 | 77 | # BASEBALL 78 | # chosen_date = "2021-04-01" # MLB - 2021 79 | # chosen_date = "2022-04-10" # MLB - 2022 80 | 81 | # BASKETBALL 82 | # chosen_date = "2023-11-26" 83 | 84 | return chosen_date 85 | 86 | 87 | test_chosen_date = get_chosen_date() 88 | 89 | 90 | # set desired Yahoo Fantasy Sports game code 91 | def get_game_code(): 92 | # FOOTBALL 93 | game_code = "nfl" # NFL 94 | 95 | # HOCKEY 96 | # game_code = "nhl" # NHL 97 | 98 | # BASEBALL 99 | # game_code = "mlb" # MLB 100 | 101 | # BASKETBALL 102 | # game_code = "nba" # NBA 103 | 104 | return game_code 105 | 106 | 107 | test_game_code = get_game_code() 108 | 109 | 110 | # set desired Yahoo Fantasy Sports game ID (see the get_all_yahoo_fantasy_game_keys query to retrieve values) 111 | def get_game_id(): 112 | # FOOTBALL 113 | # game_id = 331 # NFL - 2014 114 | # game_id = 348 # NFL - 2015 (divisions) 115 | # game_id = 359 # NFL - 2016 116 | # game_id = 371 # NFL - 2017 117 | # game_id = 380 # NFL - 2018 118 | # game_id = 390 # NFL - 2019 119 | # game_id = 399 # NFL - 2020 120 | # game_id = 406 # NFL - 2021 121 | # game_id = 414 # NFL - 2022 (divisions) 122 | # game_id = 423 # NFL - 2023 123 | game_id = 449 # NFL - 2024 124 | 125 | # HOCKEY 126 | # game_id = 303 # NHL - 2012 127 | # game_id = 411 # NHL - 2021 128 | # game_id = 427 # NHL - 2023 129 | 130 | # BASEBALL 131 | # game_id = 404 # MLB - 2021 132 | # game_id = 412 # MLB - 2022 133 | 134 | # BASKETBALL 135 | # game_id = 428 # NBA - 2023 136 | 137 | return game_id 138 | 139 | 140 | test_game_id = get_game_id() 141 | 142 | 143 | # set desired Yahoo Fantasy Sports game key (see the get_all_yahoo_fantasy_game_keys query to retrieve values) 144 | def get_game_key(): 145 | # FOOTBALL 146 | # game_key = "331" # NFL - 2014 147 | # game_key = "348" # NFL - 2015 (divisions) 148 | # game_key = "359" # NFL - 2016 149 | # game_key = "371" # NFL - 2017 150 | # game_key = "380" # NFL - 2018 151 | # game_key = "390" # NFL - 2019 152 | # game_key = "399" # NFL - 2020 153 | # game_key = "406" # NFL - 2021 154 | # game_key = "414" # NFL - 2022 (divisions) 155 | # game_key = "423" # NFL - 2023 156 | game_key = "449" # NFL - 2024 157 | 158 | # HOCKEY 159 | # game_key = "303" # NHL - 2012 160 | # game_key = "411" # NHL - 2021 161 | # game_key = "427" # NHL - 2023 162 | 163 | # BASEBALL 164 | # game_key = "404" # MLB - 2021 165 | # game_key = "412" # MLB - 2022 166 | 167 | # BASKETBALL 168 | # game_key = "428" # NBA - 2023 169 | 170 | return game_key 171 | 172 | 173 | test_game_key = get_game_key() 174 | 175 | 176 | # set desired league ID (see README.md for finding value) 177 | def get_league_id(): 178 | # FOOTBALL 179 | # league_id = "907359" # NFL - 2015 (divisions) 180 | # league_id = "79230" # NFL - 2019 181 | # league_id = "655434" # NFL - 2020 182 | # league_id = "413954" # NFL - 2021 183 | # league_id = "791337" # NFL - 2022 (divisions) 184 | # league_id = "321958" # NFL - 2023 185 | league_id = "365083" # NFL - 2024 186 | 187 | # HOCKEY 188 | # league_id = "69624" # NHL - 2012 189 | # league_id = "101592" # NHL - 2021 190 | # league_id = "6546" # NHL - 2021 (draft pick trading) 191 | # league_id = "22827" # NHL - 2023 192 | # league_id = "1031" # NHL - 2023 (FAAB) 193 | 194 | # BASEBALL 195 | # league_id = "40134" # MLB - 2021 196 | 197 | # BASKETBALL 198 | # league_id = "969" # NBA - 2023 199 | # league_id = "122731" # NBA - 2023 200 | 201 | return league_id 202 | 203 | 204 | test_league_id = get_league_id() 205 | 206 | 207 | # set desired team ID within desired league 208 | def get_team_id(): 209 | # FOOTBALL 210 | team_id = 1 # NFL 211 | 212 | # HOCKEY 213 | # team_id = 2 # NHL (2012) 214 | 215 | return team_id 216 | 217 | 218 | test_team_id = get_team_id() 219 | 220 | 221 | # set desired team name within desired league 222 | def get_team_name(): 223 | # FOOTBALL 224 | team_name = "Let Baker Bake" # NFL 225 | 226 | # HOCKEY 227 | # team_name = "The Bateleurs" # NHL (2012) 228 | 229 | return team_name 230 | 231 | 232 | test_team_name = get_team_name() 233 | 234 | 235 | # set desired team ID within desired league 236 | def get_player_id(): 237 | # FOOTBALL 238 | player_id = 30123 # NFL: Patrick Mahomes - 2020/2021/2023/2024 239 | 240 | # HOCKEY 241 | # player_id = 4588 # NHL: Braden Holtby - 2012 242 | # player_id = 8205 # NHL: Jeffrey Viel - 2021 243 | # player_id = 3637 # NHL: Alex Ovechkin - 2021 244 | 245 | # BASEBALL 246 | # player_id = 9897 # MLB: Tim Anderson - 2021/2022 247 | 248 | # BASKETBALL 249 | # player_id = 3704 # NBA: LeBron James - 2023 250 | 251 | return player_id 252 | 253 | 254 | test_player_id = get_player_id() 255 | 256 | 257 | # set the maximum number players you wish the get_league_players query to retrieve 258 | def get_league_player_limit(): 259 | league_player_limit = 101 260 | 261 | return league_player_limit 262 | 263 | 264 | test_league_player_limit = get_league_player_limit() 265 | 266 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 267 | # QUERY SETUP # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 268 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 269 | 270 | # configure the Yahoo Fantasy Sports query (change all_output_as_json_str=True if you want to output JSON strings) 271 | query = YahooFantasySportsQuery( 272 | test_league_id, 273 | test_game_code, 274 | game_id=test_game_id, 275 | yahoo_consumer_key=os.environ.get("YAHOO_CONSUMER_KEY"), 276 | yahoo_consumer_secret=os.environ.get("YAHOO_CONSUMER_SECRET"), 277 | # yahoo_access_token_json=os.environ.get("YAHOO_ACCESS_TOKEN_JSON"), 278 | env_file_location=project_dir, 279 | save_token_data_to_env_file=True 280 | ) 281 | 282 | # query.save_access_token_data_to_env_file(project_dir, save_json_to_var_only=True) 283 | 284 | # Manually override league key for example code to work 285 | query.league_key = f"{test_game_id}.l.{test_league_id}" 286 | 287 | # Manually override player key for example code to work 288 | test_player_key = f"{test_game_id}.p.{test_player_id}" 289 | 290 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 291 | # RUN QUERIES # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 292 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 293 | 294 | # print(repr(query.get_all_yahoo_fantasy_game_keys())) 295 | # print(repr(query.get_game_key_by_season(test_season))) 296 | # print(repr(query.get_current_game_info())) 297 | # print(repr(query.get_current_game_metadata())) 298 | # print(repr(query.get_game_info_by_game_id(test_game_id))) 299 | # print(repr(query.get_game_metadata_by_game_id(test_game_id))) 300 | # print(repr(query.get_game_weeks_by_game_id(test_game_id))) 301 | # print(repr(query.get_game_stat_categories_by_game_id(test_game_id))) 302 | # print(repr(query.get_game_position_types_by_game_id(test_game_id))) 303 | # print(repr(query.get_game_roster_positions_by_game_id(test_game_id))) 304 | # print(repr(query.get_league_key(test_season))) 305 | # print(repr(query.get_current_user())) 306 | # print(repr(query.get_user_games())) 307 | # print(repr(query.get_user_leagues_by_game_key(test_game_key))) 308 | # print(repr(query.get_user_teams())) 309 | # print(repr(query.get_league_info())) 310 | # print(repr(query.get_league_metadata())) 311 | # print(repr(query.get_league_settings())) 312 | # print(repr(query.get_league_standings())) 313 | # print(repr(query.get_league_teams())) 314 | # print(repr(query.get_league_players(player_count_limit=10, player_count_start=0))) 315 | # print(repr(query.get_league_draft_results())) 316 | # print(repr(query.get_league_transactions())) 317 | # print(repr(query.get_league_scoreboard_by_week(test_chosen_week))) 318 | # print(repr(query.get_league_matchups_by_week(test_chosen_week))) 319 | # print(repr(query.get_team_info(test_team_id))) 320 | # print(repr(query.get_team_metadata(test_team_id))) 321 | # print(repr(query.get_team_stats(test_team_id))) 322 | # print(repr(query.get_team_stats_by_week(test_team_id, test_chosen_week))) 323 | # print(repr(query.get_team_standings(test_team_id))) 324 | # print(repr(query.get_team_roster_by_week(test_team_id, test_chosen_week))) 325 | # print(repr(query.get_team_roster_player_info_by_week(test_team_id, test_chosen_week))) 326 | # # print(repr(query.get_team_roster_player_info_by_date(test_team_id, test_chosen_date))) # NHL/MLB/NBA 327 | # print(repr(query.get_team_roster_player_stats(test_team_id))) 328 | # print(repr(query.get_team_roster_player_stats_by_week(test_team_id, test_chosen_week))) 329 | # print(repr(query.get_team_draft_results(test_team_id))) 330 | # print(repr(query.get_team_matchups(test_team_id))) 331 | # print(repr(query.get_player_stats_for_season(test_player_key))) 332 | # print(repr(query.get_player_stats_for_season(test_player_key, limit_to_league_stats=False))) 333 | # print(repr(query.get_player_stats_by_week(test_player_key, test_chosen_week))) 334 | # print(repr(query.get_player_stats_by_week(test_player_key, test_chosen_week, limit_to_league_stats=False))) 335 | # print(repr(query.get_player_stats_by_date(test_player_key, test_chosen_date))) # NHL/MLB/NBA 336 | # print(repr(query.get_player_stats_by_date(test_player_key, test_chosen_date, limit_to_league_stats=False))) # NHL/MLB/NBA # noqa: E501 337 | # print(repr(query.get_player_ownership(test_player_key))) 338 | # print(repr(query.get_player_percent_owned_by_week(test_player_key, test_chosen_week))) 339 | # print(repr(query.get_player_draft_analysis(test_player_key))) 340 | 341 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 342 | # CHECK FOR MISSING DATA FIELDS # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 343 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 344 | 345 | logger = get_logger("yfpy.models", DEBUG) 346 | 347 | # query.get_all_yahoo_fantasy_game_keys() 348 | # query.get_game_key_by_season(test_season) 349 | # query.get_current_game_info() 350 | # query.get_current_game_metadata() 351 | # query.get_game_info_by_game_id(test_game_id) 352 | # query.get_game_metadata_by_game_id(test_game_id) 353 | # query.get_game_weeks_by_game_id(test_game_id) 354 | # query.get_game_stat_categories_by_game_id(test_game_id) 355 | # query.get_game_position_types_by_game_id(test_game_id) 356 | # query.get_game_roster_positions_by_game_id(test_game_id) 357 | # query.get_league_key(test_season) 358 | # query.get_current_user() 359 | # query.get_user_games() 360 | # query.get_user_leagues_by_game_key(test_game_key) 361 | # query.get_user_teams() 362 | # query.get_league_info() 363 | # query.get_league_metadata() 364 | # query.get_league_settings() 365 | # query.get_league_standings() 366 | # query.get_league_teams() 367 | # query.get_league_players(player_count_limit=10, player_count_start=0) 368 | # query.get_league_draft_results() 369 | # query.get_league_transactions() 370 | # query.get_league_scoreboard_by_week(test_chosen_week) 371 | # query.get_league_matchups_by_week(test_chosen_week) 372 | # query.get_team_info(test_team_id) 373 | # query.get_team_metadata(test_team_id) 374 | # query.get_team_stats(test_team_id) 375 | # query.get_team_stats_by_week(test_team_id, test_chosen_week) 376 | # query.get_team_standings(test_team_id) 377 | # query.get_team_roster_by_week(test_team_id, test_chosen_week) 378 | # query.get_team_roster_player_info_by_week(test_team_id, test_chosen_week) 379 | # # query.get_team_roster_player_info_by_date(test_team_id, test_chosen_date) # NHL/MLB/NBA 380 | # query.get_team_roster_player_stats(test_team_id) 381 | # query.get_team_roster_player_stats_by_week(test_team_id, test_chosen_week) 382 | # query.get_team_draft_results(test_team_id) 383 | # query.get_team_matchups(test_team_id) 384 | # query.get_player_stats_for_season(test_player_key) 385 | # query.get_player_stats_for_season(test_player_key, limit_to_league_stats=False) 386 | # query.get_player_stats_by_week(test_player_key, test_chosen_week) 387 | # query.get_player_stats_by_week(test_player_key, test_chosen_week, limit_to_league_stats=False) 388 | # query.get_player_stats_by_date(test_player_key, test_chosen_date) # NHL/MLB/NBA 389 | # query.get_player_stats_by_date(test_player_key, test_chosen_date, limit_to_league_stats=False) # NHL/MLB/NBA 390 | # query.get_player_ownership(test_player_key) 391 | # query.get_player_percent_owned_by_week(test_player_key, test_chosen_week) 392 | # query.get_player_draft_analysis(test_player_key) 393 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | black==24.8.0 2 | build==1.2.2 3 | mkdocs==1.6.1 4 | mkdocs-autorefs==1.2.0 5 | mkdocstrings-python==1.11.1 6 | pymdown-extensions==10.10 7 | setuptools==75.1.0 8 | twine==5.1.1 9 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | bandit==1.7.9 2 | coverage==7.6.1 3 | flake8==7.1.1 4 | pytest==8.3.3 5 | python-dotenv==1.0.1 6 | requests==2.32.3 7 | ruamel.yaml==0.18.6 8 | stringcase==1.2.0 9 | yahoo-oauth==2.1.0 10 | -------------------------------------------------------------------------------- /resources/documentation/CNAME: -------------------------------------------------------------------------------- 1 | yfpy.uberfastman.com -------------------------------------------------------------------------------- /resources/images/donate-bitcoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uberfastman/yfpy/095cc5e3cd88a56b1a337f3169001f35a901c6f9/resources/images/donate-bitcoin.png -------------------------------------------------------------------------------- /resources/images/donate-ethereum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uberfastman/yfpy/095cc5e3cd88a56b1a337f3169001f35a901c6f9/resources/images/donate-ethereum.png -------------------------------------------------------------------------------- /resources/images/donate-paypal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uberfastman/yfpy/095cc5e3cd88a56b1a337f3169001f35a901c6f9/resources/images/donate-paypal.png -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description_file = README.md 3 | 4 | [flake8] 5 | ignore = 6 | # 1 blank line required before class docstring 7 | D203, 8 | # line break occurred before a binary operator 9 | W503 10 | per-file-ignores = 11 | # imported but unused 12 | __init__.py: F401 13 | data.py: F401 14 | # add to PYTHONPATH before imports 15 | quickstart.py: E402 16 | exclude = 17 | __pycache__, 18 | .git, 19 | .github, 20 | .idea, 21 | .pytest_cache, 22 | build, 23 | dist, 24 | yfpy.egg-info, 25 | test/test_output 26 | max-complexity = 15 27 | max-line-length = 120 28 | 29 | [tool:pytest] 30 | markers = 31 | unit: tests that are self-contained without any external interactions, 32 | integration: tests that require interaction with external components (such as an API) 33 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import subprocess 4 | from pathlib import Path 5 | 6 | import setuptools 7 | from ruamel.yaml import YAML 8 | 9 | from VERSION_PYTHON import __version_minimum_python__, __version_maximum_python__ 10 | 11 | project_root_dir = Path(__file__).parent 12 | 13 | version_file = project_root_dir / "VERSION.py" 14 | 15 | # noinspection PyBroadException 16 | try: 17 | git_version = subprocess.check_output(["git", "describe", "--tag", "--abbrev=0"]).decode("utf-8").strip() 18 | pypi_version = git_version[1:] 19 | except Exception: 20 | with open(version_file, "r") as vf: 21 | git_version = vf.read().strip().split("=")[-1].strip().replace('"', '') 22 | pypi_version = git_version[1:] 23 | 24 | with open(version_file, "w") as vf: 25 | print("Updating \"VERSION.py\" with YFPY version from git tag before packaging...") 26 | vf.write( 27 | f"# DO NOT EDIT - VERSIONING CONTROLLED BY GIT TAGS{os.linesep}__version__ = \"{git_version}\"{os.linesep}") 28 | 29 | if Path("build").exists(): 30 | print("Removing stale \"build\" directory before packaging...") 31 | shutil.rmtree("build") 32 | 33 | if Path("dist").exists(): 34 | print("Removing stale \"dist\" directory before packaging...") 35 | shutil.rmtree("dist") 36 | 37 | if Path("yfpy.egg-info").exists(): 38 | print("Removing stale \"yfpy.egg-info\" directory before packaging...") 39 | shutil.rmtree("yfpy.egg-info") 40 | 41 | with open("README.md", "r", encoding="utf8") as docs: 42 | long_description = docs.read() 43 | 44 | with open("requirements.txt", "r", encoding="utf8") as reqs: 45 | required = reqs.read().splitlines() 46 | 47 | supported_python_major_versions = [ 48 | version for version in 49 | range(int(__version_minimum_python__.split(".")[0]), (int(__version_maximum_python__.split(".")[0]) + 1)) 50 | ] 51 | 52 | supported_python_minor_versions = [ 53 | version for version in 54 | range(int(__version_minimum_python__.split(".")[-1]), (int(__version_maximum_python__.split(".")[-1]) + 1)) 55 | ] 56 | 57 | docker_compose_yaml_file = project_root_dir / "compose.yaml" 58 | if docker_compose_yaml_file.exists(): 59 | print("Updating \"compose.yaml\" with YFPY version from git tag before packaging...") 60 | 61 | yaml = YAML(typ="rt") 62 | docker_compose_yaml = yaml.load(docker_compose_yaml_file) 63 | docker_compose_yaml["services"]["package"]["image"] = ( 64 | f"{docker_compose_yaml['services']['package']['image'].split(':')[0]}:{git_version.replace('v', '')}" 65 | ) 66 | 67 | yaml.default_flow_style = False 68 | yaml.dump(docker_compose_yaml, docker_compose_yaml_file) 69 | 70 | docker_compose_build_yaml_file = project_root_dir / "compose.build.yaml" 71 | if docker_compose_build_yaml_file.exists(): 72 | print("Updating \"compose.build.yaml\" with Python version from .env before packaging...") 73 | 74 | yaml = YAML(typ="rt") 75 | docker_compose_build_yaml = yaml.load(docker_compose_build_yaml_file) 76 | 77 | updated_build_args = [] 78 | for build_arg in docker_compose_build_yaml["services"]["package"]["build"]["args"]: 79 | build_arg_key, build_arg_value = build_arg.split("=") 80 | if build_arg_key == "PYTHON_VERSION_MAJOR": 81 | build_arg_value = supported_python_major_versions[0] 82 | elif build_arg_key == "PYTHON_VERSION_MINOR": 83 | build_arg_value = supported_python_minor_versions[-1] 84 | updated_build_args.append(f"{build_arg_key}={build_arg_value}") 85 | docker_compose_build_yaml["services"]["package"]["build"]["args"] = updated_build_args 86 | 87 | yaml.default_flow_style = False 88 | yaml.dump(docker_compose_build_yaml, docker_compose_build_yaml_file) 89 | 90 | setuptools.setup( 91 | name="yfpy", 92 | version=pypi_version, 93 | author="Wren J. R.", 94 | author_email="uberfastman@uberfastman.dev", 95 | description="Python API wrapper for the Yahoo Fantasy Sports public API.", 96 | long_description=long_description, 97 | long_description_content_type="text/markdown", 98 | keywords="yahoo fantasy sports api wrapper nfl football nhl hockey mlb baseball nba basketball", 99 | url="https://github.com/uberfastman/yfpy", 100 | download_url=f"https://github.com/uberfastman/yfpy/archive/{git_version}.tar.gz", 101 | packages=setuptools.find_packages(), 102 | classifiers=[ 103 | f"Programming Language :: Python :: {supported_python_major_versions[0]}", 104 | *[ 105 | f"Programming Language :: Python :: {supported_python_major_versions[0]}.{version}" 106 | for version in supported_python_minor_versions 107 | ], 108 | "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", 109 | "Operating System :: OS Independent", 110 | "Development Status :: 5 - Production/Stable", 111 | "Topic :: Software Development :: Libraries :: Python Modules", 112 | "Environment :: Console", 113 | "Intended Audience :: Developers" 114 | ], 115 | python_requires=f">={__version_minimum_python__}", 116 | install_requires=required 117 | ) 118 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uberfastman/yfpy/095cc5e3cd88a56b1a337f3169001f35a901c6f9/test/__init__.py -------------------------------------------------------------------------------- /test/conftest.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Pytest top-level conftest.py. 3 | 4 | """ 5 | __author__ = "Wren J. R. (uberfastman)" 6 | __email__ = "uberfastman@uberfastman.dev" 7 | 8 | import pytest 9 | 10 | 11 | @pytest.fixture 12 | def show_log_output() -> bool: 13 | """Turn on/off example code stdout logging output. 14 | 15 | Returns: 16 | bool: Boolean value representing if log output is turned on or off. 17 | 18 | """ 19 | log_output = False 20 | return log_output 21 | -------------------------------------------------------------------------------- /test/integration/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Pytest integration tests for YFPY. 3 | 4 | """ 5 | __author__ = "Wren J. R. (uberfastman)" 6 | __email__ = "uberfastman@uberfastman.dev" 7 | -------------------------------------------------------------------------------- /test/integration/conftest.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Pytest integration test conftest.py. 3 | 4 | """ 5 | __author__ = "Wren J. R. (uberfastman)" 6 | __email__ = "uberfastman@uberfastman.dev" 7 | 8 | import os 9 | from pathlib import Path 10 | from typing import Union 11 | from quickstart import quickstart 12 | 13 | import pytest 14 | 15 | from yfpy import Data 16 | from yfpy.query import YahooFantasySportsQuery 17 | 18 | """ 19 | Example public Yahoo league URL: "https://archive.fantasysports.yahoo.com/nfl/2014/729259" 20 | 21 | Example vars using public Yahoo leagues still require auth through a personal Yahoo account: see README.md 22 | """ 23 | 24 | 25 | @pytest.fixture 26 | def data_dir() -> Path: 27 | """Code tests will output data to this directory.""" 28 | return Path(__file__).parent / "test_output" 29 | 30 | 31 | @pytest.fixture 32 | def yahoo_data(data_dir: Union[Path, str]) -> Data: 33 | """Instantiate yfpy Data object.""" 34 | return Data(data_dir) 35 | 36 | 37 | @pytest.fixture 38 | def yahoo_query(league_id: str, game_code: str, game_id: int, browser_callback: bool) -> YahooFantasySportsQuery: 39 | """Instantiate yfpy YahooFantasySportsQuery object and override league key.""" 40 | yahoo_query = YahooFantasySportsQuery( 41 | league_id, 42 | game_code, 43 | game_id=game_id, 44 | yahoo_consumer_key=os.environ.get("YFPY_CONSUMER_KEY"), 45 | yahoo_consumer_secret=os.environ.get("YFPY_CONSUMER_SECRET"), 46 | env_file_location=Path(__file__).parent.parent.parent / "env", 47 | all_output_as_json_str=False, 48 | browser_callback=browser_callback, 49 | offline=False, 50 | ) 51 | 52 | # Manually override league key for example code to work 53 | yahoo_query.league_key = f"{game_id}.l.{league_id}" 54 | 55 | return yahoo_query 56 | 57 | 58 | @pytest.fixture 59 | def browser_callback() -> bool: 60 | """Turn on/off automatic opening of browser window for OAuth.""" 61 | browser_callback = True 62 | return browser_callback 63 | 64 | 65 | @pytest.fixture 66 | def season() -> int: 67 | """Set Yahoo Fantasy Sports season for testing.""" 68 | return quickstart.get_season() 69 | 70 | 71 | @pytest.fixture 72 | def chosen_week() -> int: 73 | """Set Yahoo Fantasy Sports chosen week for testing.""" 74 | return quickstart.get_chosen_week() 75 | 76 | 77 | @pytest.fixture 78 | def chosen_date() -> str: 79 | """Set Yahoo Fantasy Sports chosen date for testing.""" 80 | return quickstart.get_chosen_date() 81 | 82 | 83 | @pytest.fixture 84 | def game_code() -> str: 85 | """Set Yahoo Fantasy Sports game code for testing.""" 86 | return quickstart.get_game_code() 87 | 88 | 89 | @pytest.fixture 90 | def game_id() -> int: 91 | """Set Yahoo Fantasy Sports game ID for testing.""" 92 | return quickstart.get_game_id() 93 | 94 | 95 | @pytest.fixture 96 | def game_key() -> str: 97 | """Set Yahoo Fantasy Sports game key for testing.""" 98 | return quickstart.get_game_key() 99 | 100 | 101 | @pytest.fixture 102 | def league_id() -> str: 103 | """Set Yahoo Fantasy Sports league ID for testing.""" 104 | return quickstart.get_league_id() 105 | 106 | 107 | @pytest.fixture 108 | def team_id() -> int: 109 | """Set Yahoo Fantasy Sports team ID for testing.""" 110 | return quickstart.get_team_id() 111 | 112 | 113 | @pytest.fixture 114 | def team_name() -> str: 115 | """Set Yahoo Fantasy Sports team name for testing.""" 116 | return quickstart.get_team_name() 117 | 118 | 119 | @pytest.fixture 120 | def player_id() -> int: 121 | """Create and set Yahoo Fantasy Sports player ID for testing.""" 122 | return quickstart.get_player_id() 123 | 124 | 125 | @pytest.fixture 126 | def player_key(game_id, player_id) -> str: 127 | """Create and set Yahoo Fantasy Sports player key for testing.""" 128 | 129 | player_key = f"{game_id}.p.{player_id}" 130 | 131 | return player_key 132 | 133 | 134 | @pytest.fixture 135 | def league_player_limit() -> int: 136 | """Set Yahoo Fantasy Sports league player retrieval limit for testing.""" 137 | return quickstart.get_league_player_limit() 138 | -------------------------------------------------------------------------------- /test/integration/test_api_game_data.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Pytest integration tests for Yahoo Fantasy Sports API game data. 3 | 4 | Note: 5 | Tests saving and loading all Yahoo Fantasy Sports API game data. 6 | 7 | Attributes: 8 | logger (Logger): Game data integration tests logger. 9 | 10 | """ 11 | __author__ = "Wren J. R. (uberfastman)" 12 | __email__ = "uberfastman@uberfastman.dev" 13 | 14 | import logging 15 | import warnings 16 | 17 | import pytest 18 | 19 | from yfpy.logger import get_logger 20 | from yfpy.models import Game, StatCategories 21 | from yfpy.utils import prettify_data 22 | 23 | logger = get_logger(__name__) 24 | 25 | # Suppress YahooFantasySportsQuery debug logging 26 | logging.getLogger("yfpy.query").setLevel(level=logging.INFO) 27 | 28 | # Ignore resource warnings from unittest module 29 | warnings.simplefilter("ignore", ResourceWarning) 30 | 31 | 32 | @pytest.mark.integration 33 | def test_get_all_yahoo_fantasy_game_keys(yahoo_query, yahoo_data, game_code, game_id, show_log_output): 34 | """Integration test for retrieval of all Yahoo fantasy football game keys. 35 | 36 | Note: 37 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_all_yahoo_fantasy_game_keys`. 38 | 39 | """ 40 | query_result_data = yahoo_data.save( 41 | f"{game_code}-game_keys", 42 | yahoo_query.get_all_yahoo_fantasy_game_keys 43 | ) 44 | if show_log_output: 45 | logger.info(prettify_data(query_result_data)) 46 | 47 | loaded_result_data = yahoo_data.load( 48 | f"{game_code}-game_keys", 49 | all_output_as_json_str=yahoo_query.all_output_as_json_str 50 | ) 51 | if show_log_output: 52 | logger.info(f"{prettify_data(loaded_result_data)}\n----------\n") 53 | 54 | assert query_result_data == loaded_result_data 55 | 56 | 57 | @pytest.mark.integration 58 | def test_get_game_key_by_season(yahoo_query, season, game_key, show_log_output): 59 | """Integration test for retrieval of specific game key by season. 60 | 61 | Note: 62 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_game_key_by_season`. 63 | 64 | """ 65 | query_result_data = yahoo_query.get_game_key_by_season(season=season) 66 | if show_log_output: 67 | logger.info(prettify_data(query_result_data)) 68 | 69 | assert query_result_data == game_key 70 | 71 | 72 | @pytest.mark.integration 73 | def test_get_current_game_info(yahoo_query, yahoo_data, data_dir, season, game_id, show_log_output): 74 | """Integration test for retrieval of game info for current fantasy season. 75 | 76 | Note: 77 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_current_game_info`. 78 | 79 | """ 80 | query_result_data = yahoo_data.save( 81 | "current-game-info", 82 | yahoo_query.get_current_game_info 83 | ) 84 | if show_log_output: 85 | logger.info(prettify_data(query_result_data)) 86 | 87 | loaded_result_data = yahoo_data.load( 88 | "current-game-info", 89 | Game, 90 | all_output_as_json_str=yahoo_query.all_output_as_json_str 91 | ) 92 | if show_log_output: 93 | logger.info(prettify_data(loaded_result_data)) 94 | 95 | assert query_result_data == loaded_result_data 96 | 97 | 98 | @pytest.mark.integration 99 | def test_get_current_game_metadata(yahoo_query, yahoo_data, data_dir, season, game_id, show_log_output): 100 | """Integration test for retrieval of game metadata for current fantasy season. 101 | 102 | Note: 103 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_current_game_metadata`. 104 | 105 | """ 106 | query_result_data = yahoo_data.save( 107 | "current-game-metadata", 108 | yahoo_query.get_current_game_metadata 109 | ) 110 | if show_log_output: 111 | logger.info(prettify_data(query_result_data)) 112 | 113 | loaded_result_data = yahoo_data.load( 114 | "current-game-metadata", 115 | Game, 116 | all_output_as_json_str=yahoo_query.all_output_as_json_str 117 | ) 118 | if show_log_output: 119 | logger.info(prettify_data(loaded_result_data)) 120 | 121 | assert query_result_data == loaded_result_data 122 | 123 | 124 | @pytest.mark.integration 125 | def test_get_game_info_by_game_id(yahoo_query, yahoo_data, data_dir, season, game_id, show_log_output): 126 | """Integration test for retrieval of game info for specific game by id. 127 | 128 | Note: 129 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_game_info_by_game_id`. 130 | 131 | """ 132 | new_data_dir = data_dir / str(season) 133 | query_result_data = yahoo_data.save( 134 | f"{game_id}-game-info", 135 | yahoo_query.get_game_info_by_game_id, 136 | params={"game_id": game_id}, 137 | new_data_dir=new_data_dir 138 | ) 139 | if show_log_output: 140 | logger.info(prettify_data(query_result_data)) 141 | 142 | loaded_result_data = yahoo_data.load( 143 | f"{game_id}-game-info", 144 | Game, 145 | new_data_dir=new_data_dir, 146 | all_output_as_json_str=yahoo_query.all_output_as_json_str 147 | ) 148 | if show_log_output: 149 | logger.info(prettify_data(loaded_result_data)) 150 | 151 | assert query_result_data == loaded_result_data 152 | 153 | 154 | @pytest.mark.integration 155 | def test_get_game_metadata_by_game_id(yahoo_query, yahoo_data, data_dir, season, game_id, show_log_output): 156 | """Integration test for retrieval of game metadata for specific game by id. 157 | 158 | Note: 159 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_game_metadata_by_game_id`. 160 | 161 | """ 162 | new_data_dir = data_dir / str(season) 163 | query_result_data = yahoo_data.save( 164 | f"{game_id}-game-metadata", 165 | yahoo_query.get_game_metadata_by_game_id, 166 | params={"game_id": game_id}, 167 | new_data_dir=new_data_dir 168 | ) 169 | if show_log_output: 170 | logger.info(prettify_data(query_result_data)) 171 | 172 | loaded_result_data = yahoo_data.load( 173 | f"{game_id}-game-metadata", 174 | Game, 175 | new_data_dir=new_data_dir, 176 | all_output_as_json_str=yahoo_query.all_output_as_json_str 177 | ) 178 | if show_log_output: 179 | logger.info(prettify_data(loaded_result_data)) 180 | 181 | assert query_result_data == loaded_result_data 182 | 183 | 184 | @pytest.mark.integration 185 | def test_get_league_key(yahoo_query, yahoo_data, data_dir, season, game_id, league_id, show_log_output): 186 | """Integration test for retrieval of league key for selected league. 187 | 188 | Note: 189 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_league_key`. 190 | 191 | """ 192 | query_result_data = yahoo_query.get_league_key() 193 | if show_log_output: 194 | logger.info(prettify_data(query_result_data)) 195 | 196 | assert query_result_data == f"{game_id}.l.{league_id}" 197 | 198 | 199 | @pytest.mark.integration 200 | def test_get_game_weeks_by_game_id(yahoo_query, yahoo_data, data_dir, season, game_id, show_log_output): 201 | """Integration test for retrieval of all valid weeks of a specific game by id. 202 | 203 | Note: 204 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_game_weeks_by_game_id`. 205 | 206 | """ 207 | new_data_dir = data_dir / str(season) 208 | query_result_data = yahoo_data.save( 209 | f"{game_id}-game-weeks", 210 | yahoo_query.get_game_weeks_by_game_id, 211 | params={"game_id": game_id}, 212 | new_data_dir=new_data_dir 213 | ) 214 | if show_log_output: 215 | logger.info(prettify_data(query_result_data)) 216 | 217 | loaded_result_data = yahoo_data.load( 218 | f"{game_id}-game-weeks", 219 | new_data_dir=new_data_dir, 220 | all_output_as_json_str=yahoo_query.all_output_as_json_str 221 | ) 222 | if show_log_output: 223 | logger.info(prettify_data(loaded_result_data)) 224 | 225 | assert query_result_data == loaded_result_data 226 | 227 | 228 | @pytest.mark.integration 229 | def test_get_game_stat_categories_by_game_id(yahoo_query, yahoo_data, data_dir, season, game_id, show_log_output): 230 | """Integration test for retrieval of all valid stat categories of a specific game by id. 231 | 232 | Note: 233 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_game_stat_categories_by_game_id`. 234 | 235 | """ 236 | new_data_dir = data_dir / str(season) 237 | query_result_data = yahoo_data.save( 238 | f"{game_id}-game-stat_categories", 239 | yahoo_query.get_game_stat_categories_by_game_id, 240 | params={"game_id": game_id}, 241 | new_data_dir=new_data_dir 242 | ) 243 | if show_log_output: 244 | logger.info(prettify_data(query_result_data)) 245 | 246 | loaded_result_data = yahoo_data.load( 247 | f"{game_id}-game-stat_categories", 248 | StatCategories, 249 | new_data_dir=new_data_dir, 250 | all_output_as_json_str=yahoo_query.all_output_as_json_str 251 | ) 252 | if show_log_output: 253 | logger.info(prettify_data(query_result_data)) 254 | 255 | assert query_result_data == loaded_result_data 256 | 257 | 258 | @pytest.mark.integration 259 | def test_get_game_position_types_by_game_id(yahoo_query, yahoo_data, data_dir, season, game_id, show_log_output): 260 | """Integration test for retrieval of all valid position types for specific game by id. 261 | 262 | Note: 263 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_game_position_types_by_game_id`. 264 | 265 | """ 266 | new_data_dir = data_dir / str(season) 267 | query_result_data = yahoo_data.save( 268 | f"{game_id}-game-position_types", 269 | yahoo_query.get_game_position_types_by_game_id, 270 | params={"game_id": game_id}, 271 | new_data_dir=new_data_dir 272 | ) 273 | if show_log_output: 274 | logger.info(prettify_data(query_result_data)) 275 | 276 | loaded_result_data = yahoo_data.load( 277 | f"{game_id}-game-position_types", 278 | new_data_dir=new_data_dir, 279 | all_output_as_json_str=yahoo_query.all_output_as_json_str 280 | ) 281 | if show_log_output: 282 | logger.info(prettify_data(loaded_result_data)) 283 | 284 | assert query_result_data == loaded_result_data 285 | 286 | 287 | @pytest.mark.integration 288 | def test_get_game_roster_positions_by_game_id(yahoo_query, yahoo_data, data_dir, season, game_id, show_log_output): 289 | """Integration test for retrieval of all valid roster positions for specific game by id. 290 | 291 | Note: 292 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_game_roster_positions_by_game_id`. 293 | 294 | """ 295 | new_data_dir = data_dir / str(season) 296 | query_result_data = yahoo_data.save( 297 | f"{game_id}-game-roster_positions", 298 | yahoo_query.get_game_roster_positions_by_game_id, 299 | params={"game_id": game_id}, 300 | new_data_dir=new_data_dir 301 | ) 302 | if show_log_output: 303 | logger.info(prettify_data(query_result_data)) 304 | 305 | loaded_result_data = yahoo_data.load( 306 | f"{game_id}-game-roster_positions", 307 | new_data_dir=new_data_dir, 308 | all_output_as_json_str=yahoo_query.all_output_as_json_str 309 | ) 310 | if show_log_output: 311 | logger.info(prettify_data(loaded_result_data)) 312 | 313 | assert query_result_data == loaded_result_data 314 | -------------------------------------------------------------------------------- /test/integration/test_api_league_data.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Pytest integration tests for Yahoo Fantasy Sports API league data. 3 | 4 | Note: 5 | Tests saving and loading all Yahoo Fantasy Sports API league data. 6 | 7 | Attributes: 8 | logger (Logger): Game data integration tests logger. 9 | 10 | """ 11 | __author__ = "Wren J. R. (uberfastman)" 12 | __email__ = "uberfastman@uberfastman.dev" 13 | 14 | import logging 15 | import warnings 16 | 17 | import pytest 18 | 19 | from yfpy.logger import get_logger 20 | from yfpy.models import Scoreboard, Settings, Standings, League 21 | from yfpy.utils import prettify_data 22 | 23 | logger = get_logger(__name__) 24 | 25 | # Suppress YahooFantasySportsQuery debug logging 26 | logging.getLogger("yfpy.query").setLevel(level=logging.INFO) 27 | 28 | # Ignore resource warnings from unittest module 29 | warnings.simplefilter("ignore", ResourceWarning) 30 | 31 | 32 | @pytest.mark.integration 33 | def test_get_league_info(yahoo_query, yahoo_data, data_dir, season, game_id, league_id, show_log_output): 34 | """Integration test for retrieval of info for chosen Yahoo fantasy league. 35 | 36 | Note: 37 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_league_info`. 38 | 39 | """ 40 | 41 | new_data_dir = data_dir / str(season) / f"{game_id}.l.{league_id}" 42 | query_result_data = yahoo_data.save( 43 | f"{league_id}-league-info", 44 | yahoo_query.get_league_info, 45 | new_data_dir=new_data_dir 46 | ) 47 | if show_log_output: 48 | logger.info(prettify_data(query_result_data)) 49 | 50 | loaded_result_data = yahoo_data.load( 51 | f"{league_id}-league-info", 52 | League, 53 | new_data_dir=new_data_dir, 54 | all_output_as_json_str=yahoo_query.all_output_as_json_str 55 | ) 56 | if show_log_output: 57 | logger.info(prettify_data(loaded_result_data)) 58 | 59 | assert query_result_data == loaded_result_data 60 | 61 | 62 | @pytest.mark.integration 63 | def test_get_league_metadata(yahoo_query, yahoo_data, data_dir, season, game_id, league_id, show_log_output): 64 | """Integration test for retrieval of metadata for chosen Yahoo fantasy league.. 65 | 66 | Note: 67 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_league_metadata`. 68 | 69 | """ 70 | new_data_dir = data_dir / str(season) / f"{game_id}.l.{league_id}" 71 | query_result_data = yahoo_data.save( 72 | f"{league_id}-league-metadata", 73 | yahoo_query.get_league_metadata, 74 | new_data_dir=new_data_dir 75 | ) 76 | if show_log_output: 77 | logger.info(prettify_data(query_result_data)) 78 | 79 | loaded_result_data = yahoo_data.load( 80 | f"{league_id}-league-metadata", 81 | League, 82 | new_data_dir=new_data_dir, 83 | all_output_as_json_str=yahoo_query.all_output_as_json_str 84 | ) 85 | if show_log_output: 86 | logger.info(prettify_data(loaded_result_data)) 87 | 88 | assert query_result_data == loaded_result_data 89 | 90 | 91 | @pytest.mark.integration 92 | def test_get_league_settings(yahoo_query, yahoo_data, data_dir, season, game_id, league_id, show_log_output): 93 | """Integration test for retrieval of settings for chosen Yahoo fantasy league. 94 | 95 | Note: 96 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_league_settings`. 97 | 98 | """ 99 | new_data_dir = data_dir / str(season) / f"{game_id}.l.{league_id}" 100 | query_result_data = yahoo_data.save( 101 | f"{league_id}-league-settings", 102 | yahoo_query.get_league_settings, 103 | new_data_dir=new_data_dir 104 | ) 105 | if show_log_output: 106 | logger.info(prettify_data(query_result_data)) 107 | 108 | loaded_result_data = yahoo_data.load( 109 | f"{league_id}-league-settings", 110 | Settings, 111 | new_data_dir=new_data_dir, 112 | all_output_as_json_str=yahoo_query.all_output_as_json_str 113 | ) 114 | if show_log_output: 115 | logger.info(prettify_data(loaded_result_data)) 116 | 117 | assert query_result_data == loaded_result_data 118 | 119 | 120 | @pytest.mark.integration 121 | def test_get_league_standings(yahoo_query, yahoo_data, data_dir, season, game_id, league_id, show_log_output): 122 | """Integration test for retrieval of standings for chosen Yahoo fantasy league. 123 | 124 | Note: 125 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_league_standings`. 126 | 127 | """ 128 | new_data_dir = data_dir / str(season) / f"{game_id}.l.{league_id}" 129 | query_result_data = yahoo_data.save( 130 | f"{league_id}-league-standings", 131 | yahoo_query.get_league_standings, 132 | new_data_dir=new_data_dir 133 | ) 134 | if show_log_output: 135 | logger.info(prettify_data(query_result_data)) 136 | 137 | loaded_result_data = yahoo_data.load( 138 | f"{league_id}-league-standings", 139 | Standings, 140 | new_data_dir=new_data_dir, 141 | all_output_as_json_str=yahoo_query.all_output_as_json_str 142 | ) 143 | if show_log_output: 144 | logger.info(prettify_data(loaded_result_data)) 145 | 146 | assert query_result_data == loaded_result_data 147 | 148 | 149 | @pytest.mark.integration 150 | def test_get_league_teams(yahoo_query, yahoo_data, data_dir, season, game_id, league_id, show_log_output): 151 | """Integration test for retrieval of all teams in chosen Yahoo fantasy league. 152 | 153 | Note: 154 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_league_teams`. 155 | 156 | """ 157 | new_data_dir = data_dir / str(season) / f"{game_id}.l.{league_id}" 158 | query_result_data = yahoo_data.save( 159 | f"{league_id}-league-teams", 160 | yahoo_query.get_league_teams, 161 | new_data_dir=new_data_dir 162 | ) 163 | if show_log_output: 164 | logger.info(prettify_data(query_result_data)) 165 | 166 | loaded_result_data = yahoo_data.load( 167 | f"{league_id}-league-teams", 168 | new_data_dir=new_data_dir, 169 | all_output_as_json_str=yahoo_query.all_output_as_json_str 170 | ) 171 | if show_log_output: 172 | logger.info(prettify_data(loaded_result_data)) 173 | 174 | assert query_result_data == loaded_result_data 175 | 176 | 177 | @pytest.mark.skip( 178 | reason="Skipping test_get_league_players: high player volume slows down tests. Run this test separately." 179 | ) 180 | @pytest.mark.integration 181 | def test_get_league_players(yahoo_query, yahoo_data, data_dir, season, game_id, league_id, show_log_output): 182 | """Integration test for retrieval of players in chosen Yahoo fantasy league. 183 | 184 | Note: 185 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_league_players`. 186 | 187 | """ 188 | new_data_dir = data_dir / str(season) / f"{game_id}.l.{league_id}" 189 | query_result_data = yahoo_data.save( 190 | f"{league_id}-league-players", 191 | yahoo_query.get_league_players, 192 | # params={"player_count_start": 1400, "player_count_limit": 1475}, 193 | new_data_dir=new_data_dir 194 | ) 195 | if show_log_output: 196 | logger.info(prettify_data(query_result_data)) 197 | 198 | loaded_result_data = yahoo_data.load( 199 | f"{league_id}-league-players", 200 | new_data_dir=new_data_dir, 201 | all_output_as_json_str=yahoo_query.all_output_as_json_str 202 | ) 203 | if show_log_output: 204 | logger.info(prettify_data(loaded_result_data)) 205 | 206 | assert query_result_data == loaded_result_data 207 | 208 | 209 | @pytest.mark.integration 210 | def test_get_league_players_with_limit(yahoo_query, yahoo_data, data_dir, season, game_id, league_id, 211 | league_player_limit, show_log_output): 212 | """Integration test for retrieval of a specified maximum of players in chosen Yahoo fantasy league. 213 | 214 | Note: 215 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_league_players`. 216 | 217 | """ 218 | new_data_dir = data_dir / str(season) / f"{game_id}.l.{league_id}" 219 | query_result_data = yahoo_data.save( 220 | f"{league_id}-league-players", 221 | yahoo_query.get_league_players, 222 | params={"player_count_limit": league_player_limit}, 223 | new_data_dir=new_data_dir 224 | ) 225 | if show_log_output: 226 | logger.info(prettify_data(query_result_data)) 227 | 228 | loaded_result_data = yahoo_data.load( 229 | f"{league_id}-league-players", 230 | new_data_dir=new_data_dir, 231 | all_output_as_json_str=yahoo_query.all_output_as_json_str 232 | ) 233 | if show_log_output: 234 | logger.info(prettify_data(loaded_result_data)) 235 | 236 | assert query_result_data == loaded_result_data 237 | 238 | 239 | @pytest.mark.integration 240 | def test_get_league_draft_results(yahoo_query, yahoo_data, data_dir, season, game_id, league_id, show_log_output): 241 | """Integration test for retrieval of draft results for chosen Yahoo fantasy league. 242 | 243 | Note: 244 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_league_draft_results`. 245 | 246 | """ 247 | new_data_dir = data_dir / str(season) / f"{game_id}.l.{league_id}" 248 | query_result_data = yahoo_data.save( 249 | f"{league_id}-league-draft_results", 250 | yahoo_query.get_league_draft_results, 251 | new_data_dir=new_data_dir 252 | ) 253 | if show_log_output: 254 | logger.info(prettify_data(query_result_data)) 255 | 256 | loaded_result_data = yahoo_data.load( 257 | f"{league_id}-league-draft_results", 258 | new_data_dir=new_data_dir, 259 | all_output_as_json_str=yahoo_query.all_output_as_json_str 260 | ) 261 | if show_log_output: 262 | logger.info(prettify_data(loaded_result_data)) 263 | 264 | assert query_result_data == loaded_result_data 265 | 266 | 267 | @pytest.mark.integration 268 | def test_get_league_transactions(yahoo_query, yahoo_data, data_dir, season, game_id, league_id, show_log_output): 269 | """Integration test for retrieval of transactions for chosen Yahoo fantasy league. 270 | 271 | Note: 272 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_league_transactions`. 273 | 274 | """ 275 | new_data_dir = data_dir / str(season) / f"{game_id}.l.{league_id}" 276 | query_result_data = yahoo_data.save( 277 | f"{league_id}-league-transactions", 278 | yahoo_query.get_league_transactions, 279 | new_data_dir=new_data_dir 280 | ) 281 | if show_log_output: 282 | logger.info(prettify_data(query_result_data)) 283 | 284 | loaded_result_data = yahoo_data.load( 285 | f"{league_id}-league-transactions", 286 | new_data_dir=new_data_dir, 287 | all_output_as_json_str=yahoo_query.all_output_as_json_str 288 | ) 289 | if show_log_output: 290 | logger.info(prettify_data(loaded_result_data)) 291 | 292 | assert query_result_data == loaded_result_data 293 | 294 | 295 | @pytest.mark.integration 296 | def test_get_league_scoreboard_by_week(yahoo_query, yahoo_data, data_dir, season, chosen_week, game_id, league_id, 297 | show_log_output): 298 | """Integration test for retrieval of scoreboard by week for chosen Yahoo fantasy league. 299 | 300 | Note: 301 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_league_scoreboard_by_week`. 302 | 303 | """ 304 | new_data_dir = data_dir / str(season) / f"{game_id}.l.{league_id}" / f"week_{chosen_week}" 305 | query_result_data = yahoo_data.save( 306 | f"week_{chosen_week}-scoreboard", 307 | yahoo_query.get_league_scoreboard_by_week, 308 | params={"chosen_week": chosen_week}, 309 | new_data_dir=new_data_dir 310 | ) 311 | if show_log_output: 312 | logger.info(prettify_data(query_result_data)) 313 | 314 | loaded_result_data = yahoo_data.load( 315 | f"week_{chosen_week}-scoreboard", 316 | Scoreboard, 317 | new_data_dir=new_data_dir, 318 | all_output_as_json_str=yahoo_query.all_output_as_json_str 319 | ) 320 | if show_log_output: 321 | logger.info(prettify_data(loaded_result_data)) 322 | 323 | assert query_result_data == loaded_result_data 324 | 325 | 326 | @pytest.mark.integration 327 | def test_get_league_matchups_by_week(yahoo_query, yahoo_data, data_dir, season, chosen_week, game_id, league_id, 328 | show_log_output): 329 | """Integration test for retrieval of matchups by week for chosen Yahoo fantasy league. 330 | 331 | Note: 332 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_league_matchups_by_week`. 333 | 334 | """ 335 | new_data_dir = data_dir / str(season) / f"{game_id}.l.{league_id}" / f"week_{chosen_week}" 336 | query_result_data = yahoo_data.save( 337 | f"week_{chosen_week}-matchups", 338 | yahoo_query.get_league_matchups_by_week, 339 | params={"chosen_week": chosen_week}, 340 | new_data_dir=new_data_dir 341 | ) 342 | if show_log_output: 343 | logger.info(prettify_data(query_result_data)) 344 | 345 | loaded_result_data = yahoo_data.load( 346 | f"week_{chosen_week}-matchups", 347 | new_data_dir=new_data_dir, 348 | all_output_as_json_str=yahoo_query.all_output_as_json_str 349 | ) 350 | if show_log_output: 351 | logger.info(prettify_data(loaded_result_data)) 352 | 353 | assert query_result_data == loaded_result_data 354 | -------------------------------------------------------------------------------- /test/integration/test_api_player_data.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Pytest integration tests for Yahoo Fantasy Sports API player data. 3 | 4 | Note: 5 | Tests saving and loading all Yahoo Fantasy Sports API player data. 6 | 7 | Attributes: 8 | logger (Logger): Game data integration tests logger. 9 | 10 | """ 11 | __author__ = "Wren J. R. (uberfastman)" 12 | __email__ = "uberfastman@uberfastman.dev" 13 | 14 | import logging 15 | import warnings 16 | 17 | import pytest 18 | 19 | from yfpy.logger import get_logger 20 | from yfpy.models import Player 21 | from yfpy.utils import prettify_data 22 | 23 | logger = get_logger(__name__) 24 | 25 | # Suppress YahooFantasySportsQuery debug logging 26 | logging.getLogger("yfpy.query").setLevel(level=logging.INFO) 27 | 28 | # Ignore resource warnings from unittest module 29 | warnings.simplefilter("ignore", ResourceWarning) 30 | 31 | 32 | @pytest.mark.integration 33 | def test_get_player_stats_for_season(yahoo_query, yahoo_data, data_dir, season, game_id, league_id, player_id, 34 | player_key, show_log_output): 35 | """Integration test for retrieval of player stats by season for chosen Yahoo fantasy league. 36 | 37 | Note: 38 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_player_stats_for_season`. 39 | 40 | """ 41 | new_data_dir = data_dir / str(season) / f"{game_id}.l.{league_id}" / "players" 42 | query_result_data = yahoo_data.save( 43 | f"{player_id}-player-season-stats", 44 | yahoo_query.get_player_stats_for_season, 45 | params={"player_key": str(player_key)}, 46 | new_data_dir=new_data_dir 47 | ) 48 | if show_log_output: 49 | logger.info(prettify_data(query_result_data)) 50 | 51 | loaded_result_data = yahoo_data.load( 52 | f"{player_id}-player-season-stats", 53 | Player, 54 | new_data_dir=new_data_dir, 55 | all_output_as_json_str=yahoo_query.all_output_as_json_str 56 | ) 57 | if show_log_output: 58 | logger.info(prettify_data(loaded_result_data)) 59 | 60 | assert query_result_data == loaded_result_data 61 | 62 | 63 | @pytest.mark.integration 64 | def test_get_player_stats_by_week(yahoo_query, yahoo_data, data_dir, season, chosen_week, game_id, league_id, 65 | player_id, player_key, show_log_output): 66 | """Integration test for retrieval of player stats by week for chosen Yahoo fantasy league.. 67 | 68 | Note: 69 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_player_stats_by_week`. 70 | 71 | """ 72 | new_data_dir = data_dir / str(season) / f"{game_id}.l.{league_id}" / f"week_{chosen_week}" / "players" 73 | query_result_data = yahoo_data.save( 74 | f"{player_id}-player-stats", 75 | yahoo_query.get_player_stats_by_week, 76 | params={"player_key": str(player_key), "chosen_week": str(chosen_week)}, 77 | new_data_dir=new_data_dir 78 | ) 79 | if show_log_output: 80 | logger.info(prettify_data(query_result_data)) 81 | 82 | loaded_result_data = yahoo_data.load( 83 | f"{player_id}-player-stats", 84 | Player, 85 | new_data_dir=new_data_dir, 86 | all_output_as_json_str=yahoo_query.all_output_as_json_str 87 | ) 88 | if show_log_output: 89 | logger.info(prettify_data(loaded_result_data)) 90 | 91 | assert query_result_data == loaded_result_data 92 | 93 | 94 | @pytest.mark.skip( 95 | reason="Skipping test_get_player_stats_by_date: retrieval by date supported by NHL/NBA/MLB, not NFL." 96 | ) 97 | @pytest.mark.integration 98 | def test_get_player_stats_by_date(yahoo_query, yahoo_data, data_dir, season, chosen_date, game_id, league_id, 99 | player_id, player_key, show_log_output): 100 | """Integration test for retrieval of player stats by date for Yahoo fantasy league. 101 | 102 | Note: 103 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_player_stats_by_date`. 104 | 105 | """ 106 | new_data_dir = data_dir / str(season) / f"{game_id}.l.{league_id}" / str(chosen_date) / "players" 107 | query_result_data = yahoo_data.save( 108 | f"{player_id}-player-stats", 109 | yahoo_query.get_player_stats_by_date, 110 | params={"player_key": str(player_key), "chosen_date": chosen_date}, 111 | new_data_dir=new_data_dir 112 | ) 113 | if show_log_output: 114 | logger.info(prettify_data(query_result_data)) 115 | 116 | loaded_result_data = yahoo_data.load( 117 | f"{player_id}-player-stats", 118 | Player, 119 | new_data_dir=new_data_dir, 120 | all_output_as_json_str=yahoo_query.all_output_as_json_str 121 | ) 122 | if show_log_output: 123 | logger.info(prettify_data(loaded_result_data)) 124 | 125 | assert query_result_data == loaded_result_data 126 | 127 | 128 | @pytest.mark.integration 129 | def test_get_player_ownership(yahoo_query, yahoo_data, data_dir, season, game_id, league_id, player_id, player_key, 130 | show_log_output): 131 | """Integration test for retrieval of ownership of chosen player for chosen Yahoo fantasy league. 132 | 133 | Note: 134 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_player_ownership`. 135 | 136 | """ 137 | new_data_dir = data_dir / str(season) / f"{game_id}.l.{league_id}" / "players" 138 | query_result_data = yahoo_data.save( 139 | f"{player_id}-player-ownership", 140 | yahoo_query.get_player_ownership, 141 | params={"player_key": str(player_key)}, 142 | new_data_dir=new_data_dir 143 | ) 144 | if show_log_output: 145 | logger.info(prettify_data(query_result_data)) 146 | 147 | loaded_result_data = yahoo_data.load( 148 | f"{player_id}-player-ownership", 149 | Player, 150 | new_data_dir=new_data_dir, 151 | all_output_as_json_str=yahoo_query.all_output_as_json_str 152 | ) 153 | if show_log_output: 154 | logger.info(prettify_data(loaded_result_data)) 155 | 156 | assert query_result_data == loaded_result_data 157 | 158 | 159 | @pytest.mark.integration 160 | def test_get_player_percent_owned_by_week(yahoo_query, yahoo_data, data_dir, season, chosen_week, game_id, league_id, 161 | player_id, player_key, show_log_output): 162 | """Integration test for retrieval of percent ownership by week of chosen player for chosen Yahoo fantasy league. 163 | 164 | Note: 165 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_player_percent_owned_by_week`. 166 | 167 | """ 168 | new_data_dir = data_dir / str(season) / f"{game_id}.l.{league_id}" / f"week_{chosen_week}" / "players" 169 | query_result_data = yahoo_data.save( 170 | f"{player_id}-player-percent_owned", 171 | yahoo_query.get_player_percent_owned_by_week, 172 | params={"player_key": str(player_key), "chosen_week": str(chosen_week)}, 173 | new_data_dir=new_data_dir 174 | ) 175 | if show_log_output: 176 | logger.info(prettify_data(query_result_data)) 177 | 178 | loaded_result_data = yahoo_data.load( 179 | f"{player_id}-player-percent_owned", 180 | Player, 181 | new_data_dir=new_data_dir, 182 | all_output_as_json_str=yahoo_query.all_output_as_json_str 183 | ) 184 | if show_log_output: 185 | logger.info(prettify_data(loaded_result_data)) 186 | 187 | assert query_result_data == loaded_result_data 188 | 189 | 190 | @pytest.mark.integration 191 | def test_get_player_draft_analysis(yahoo_query, yahoo_data, data_dir, season, game_id, league_id, player_id, 192 | player_key, show_log_output): 193 | """Integration test for retrieval of player draft analysis of chosen player for chosen Yahoo fantasy league. 194 | 195 | Note: 196 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_player_draft_analysis`. 197 | 198 | """ 199 | new_data_dir = data_dir / str(season) / f"{game_id}.l.{league_id}" / "players" 200 | query_result_data = yahoo_data.save( 201 | f"{player_id}-player-draft_analysis", 202 | yahoo_query.get_player_draft_analysis, 203 | params={"player_key": str(player_key)}, new_data_dir=new_data_dir 204 | ) 205 | if show_log_output: 206 | logger.info(prettify_data(query_result_data)) 207 | 208 | loaded_result_data = yahoo_data.load( 209 | f"{player_id}-player-draft_analysis", 210 | Player, 211 | new_data_dir=new_data_dir, 212 | all_output_as_json_str=yahoo_query.all_output_as_json_str 213 | ) 214 | if show_log_output: 215 | logger.info(prettify_data(loaded_result_data)) 216 | 217 | assert query_result_data == loaded_result_data 218 | -------------------------------------------------------------------------------- /test/integration/test_api_team_data.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Pytest integration tests for Yahoo Fantasy Sports API team data. 3 | 4 | Note: 5 | Tests saving and loading all Yahoo Fantasy Sports API team data. 6 | 7 | Attributes: 8 | logger (Logger): Game data integration tests logger. 9 | 10 | """ 11 | __author__ = "Wren J. R. (uberfastman)" 12 | __email__ = "uberfastman@uberfastman.dev" 13 | 14 | import logging 15 | import warnings 16 | 17 | import pytest 18 | 19 | from yfpy.logger import get_logger 20 | from yfpy.models import Team, TeamPoints, TeamStandings, Roster 21 | from yfpy.utils import prettify_data 22 | 23 | logger = get_logger(__name__) 24 | 25 | # Suppress YahooFantasySportsQuery debug logging 26 | logging.getLogger("yfpy.query").setLevel(level=logging.INFO) 27 | 28 | # Ignore resource warnings from unittest module 29 | warnings.simplefilter("ignore", ResourceWarning) 30 | 31 | 32 | @pytest.mark.integration 33 | def test_get_team_info(yahoo_query, yahoo_data, data_dir, season, game_id, league_id, team_id, team_name, 34 | show_log_output): 35 | """Integration test for retrieval of info for chosen team by team ID for chosen Yahoo fantasy league. 36 | 37 | Note: 38 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_team_info`. 39 | 40 | """ 41 | new_data_dir = data_dir / str(season) / f"{game_id}.l.{league_id}" / "teams" / f"{team_id}-{team_name}" 42 | query_result_data = yahoo_data.save( 43 | f"{team_id}-{team_name}-info", 44 | yahoo_query.get_team_info, 45 | params={"team_id": team_id}, 46 | new_data_dir=new_data_dir 47 | ) 48 | if show_log_output: 49 | logger.info(prettify_data(query_result_data)) 50 | 51 | loaded_result_data = yahoo_data.load( 52 | f"{team_id}-{team_name}-info", 53 | Team, 54 | new_data_dir=new_data_dir, 55 | all_output_as_json_str=yahoo_query.all_output_as_json_str 56 | ) 57 | if show_log_output: 58 | logger.info(prettify_data(loaded_result_data)) 59 | 60 | assert query_result_data == loaded_result_data 61 | 62 | 63 | @pytest.mark.integration 64 | def test_get_team_metadata(yahoo_query, yahoo_data, data_dir, season, game_id, league_id, team_id, team_name, 65 | show_log_output): 66 | """Integration test for retrieval of metadata for chosen team by team ID for chosen Yahoo fantasy league. 67 | 68 | Note: 69 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_team_metadata`. 70 | 71 | """ 72 | new_data_dir = data_dir / str(season) / f"{game_id}.l.{league_id}" / "teams" / f"{team_id}-{team_name}" 73 | query_result_data = yahoo_data.save( 74 | f"{team_id}-{team_name}-metadata", 75 | yahoo_query.get_team_metadata, 76 | params={"team_id": team_id}, 77 | new_data_dir=new_data_dir 78 | ) 79 | if show_log_output: 80 | logger.info(prettify_data(query_result_data)) 81 | 82 | loaded_result_data = yahoo_data.load( 83 | f"{team_id}-{team_name}-metadata", 84 | Team, 85 | new_data_dir=new_data_dir, 86 | all_output_as_json_str=yahoo_query.all_output_as_json_str 87 | ) 88 | if show_log_output: 89 | logger.info(prettify_data(loaded_result_data)) 90 | 91 | assert query_result_data == loaded_result_data 92 | 93 | 94 | @pytest.mark.integration 95 | def test_get_team_stats(yahoo_query, yahoo_data, data_dir, season, game_id, league_id, team_id, team_name, 96 | show_log_output): 97 | """Integration test for retrieval of stats for chosen team by team ID for chosen Yahoo fantasy league. 98 | 99 | Note: 100 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_team_stats`. 101 | 102 | """ 103 | new_data_dir = data_dir / str(season) / f"{game_id}.l.{league_id}" / "teams" / f"{team_id}-{team_name}" 104 | query_result_data = yahoo_data.save( 105 | f"{team_id}-{team_name}-stats", 106 | yahoo_query.get_team_stats, 107 | params={"team_id": team_id}, 108 | new_data_dir=new_data_dir 109 | ) 110 | if show_log_output: 111 | logger.info(prettify_data(query_result_data)) 112 | 113 | loaded_result_data = yahoo_data.load( 114 | f"{team_id}-{team_name}-stats", 115 | TeamPoints, 116 | new_data_dir=new_data_dir, 117 | all_output_as_json_str=yahoo_query.all_output_as_json_str 118 | ) 119 | if show_log_output: 120 | logger.info(prettify_data(loaded_result_data)) 121 | 122 | assert query_result_data == loaded_result_data 123 | 124 | 125 | @pytest.mark.integration 126 | def test_get_team_stats_by_week(yahoo_query, yahoo_data, data_dir, season, chosen_week, game_id, league_id, team_id, 127 | team_name, show_log_output): 128 | """Integration test for retrieval of stats for chosen team by team ID and by week for chosen Yahoo fantasy league. 129 | 130 | Note: 131 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_team_stats_by_week`. 132 | 133 | """ 134 | new_data_dir = data_dir / str(season) / f"{game_id}.l.{league_id}" / f"week_{chosen_week}" 135 | query_result_data = yahoo_data.save( 136 | f"{team_id}-{team_name}-stats", 137 | yahoo_query.get_team_stats_by_week, 138 | params={"team_id": team_id, "chosen_week": chosen_week}, 139 | new_data_dir=new_data_dir 140 | ) 141 | if show_log_output: 142 | logger.info(prettify_data(query_result_data)) 143 | 144 | loaded_result_data = yahoo_data.load( 145 | f"{team_id}-{team_name}-stats", 146 | new_data_dir=new_data_dir, 147 | all_output_as_json_str=yahoo_query.all_output_as_json_str 148 | ) 149 | if show_log_output: 150 | logger.info(prettify_data(loaded_result_data)) 151 | 152 | assert query_result_data == loaded_result_data 153 | 154 | 155 | @pytest.mark.integration 156 | def test_get_team_standings(yahoo_query, yahoo_data, data_dir, season, game_id, league_id, team_id, team_name, 157 | show_log_output): 158 | """Integration test for retrieval of standings for chosen team by team ID for chosen Yahoo fantasy league. 159 | 160 | Note: 161 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_team_standings`. 162 | 163 | """ 164 | new_data_dir = data_dir / str(season) / f"{game_id}.l.{league_id}" / "teams" / f"{team_id}-{team_name}" 165 | query_result_data = yahoo_data.save( 166 | f"{team_id}-{team_name}-standings", 167 | yahoo_query.get_team_standings, 168 | params={"team_id": team_id}, 169 | new_data_dir=new_data_dir 170 | ) 171 | if show_log_output: 172 | logger.info(prettify_data(query_result_data)) 173 | 174 | loaded_result_data = yahoo_data.load( 175 | f"{team_id}-{team_name}-standings", 176 | TeamStandings, 177 | new_data_dir=new_data_dir, 178 | all_output_as_json_str=yahoo_query.all_output_as_json_str 179 | ) 180 | if show_log_output: 181 | logger.info(prettify_data(loaded_result_data)) 182 | 183 | assert query_result_data == loaded_result_data 184 | 185 | 186 | @pytest.mark.integration 187 | def test_get_team_roster_by_week(yahoo_query, yahoo_data, data_dir, season, chosen_week, game_id, league_id, team_id, 188 | team_name, show_log_output): 189 | """Integration test for retrieval of roster for chosen team by team ID and by week for chosen Yahoo fantasy league. 190 | 191 | Note: 192 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_team_roster_by_week`. 193 | 194 | """ 195 | new_data_dir = data_dir / str(season) / f"{game_id}.l.{league_id}" / f"week_{chosen_week}" / "rosters" 196 | query_result_data = yahoo_data.save( 197 | f"{team_id}-{team_name}-roster_by_week", 198 | yahoo_query.get_team_roster_by_week, 199 | params={"team_id": team_id, "chosen_week": chosen_week}, 200 | new_data_dir=new_data_dir 201 | ) 202 | if show_log_output: 203 | logger.info(prettify_data(query_result_data)) 204 | 205 | loaded_result_data = yahoo_data.load( 206 | f"{team_id}-{team_name}-roster_by_week", 207 | Roster, 208 | new_data_dir=new_data_dir, 209 | all_output_as_json_str=yahoo_query.all_output_as_json_str 210 | ) 211 | if show_log_output: 212 | logger.info(prettify_data(loaded_result_data)) 213 | 214 | assert query_result_data == loaded_result_data 215 | 216 | 217 | @pytest.mark.integration 218 | def test_get_team_roster_player_info_by_week(yahoo_query, yahoo_data, data_dir, season, chosen_week, game_id, 219 | league_id, team_id, team_name, show_log_output): 220 | """Integration test for retrieval of roster with player info for chosen team by team ID and by week for chosen Yahoo 221 | fantasy league. 222 | 223 | Note: 224 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_team_roster_player_info_by_week`. 225 | 226 | """ 227 | new_data_dir = data_dir / str(season) / f"{game_id}.l.{league_id}" / f"week_{chosen_week}" / "rosters" 228 | query_result_data = yahoo_data.save( 229 | f"{team_id}-{team_name}-roster-player_info_by_week", 230 | yahoo_query.get_team_roster_player_info_by_week, 231 | params={"team_id": team_id, "chosen_week": chosen_week}, 232 | new_data_dir=new_data_dir 233 | ) 234 | if show_log_output: 235 | logger.info(prettify_data(query_result_data)) 236 | 237 | loaded_result_data = yahoo_data.load( 238 | f"{team_id}-{team_name}-roster-player_info_by_week", 239 | new_data_dir=new_data_dir, 240 | all_output_as_json_str=yahoo_query.all_output_as_json_str 241 | ) 242 | if show_log_output: 243 | logger.info(prettify_data(loaded_result_data)) 244 | 245 | assert query_result_data == loaded_result_data 246 | 247 | 248 | @pytest.mark.skip( 249 | reason="Skipping test_get_team_roster_player_info_by_date: retrieval by date supported by NHL/NBA/MLB, not NFL." 250 | ) 251 | @pytest.mark.integration 252 | def test_get_team_roster_player_info_by_date(yahoo_query, yahoo_data, data_dir, season, chosen_date, game_id, 253 | league_id, team_id, team_name, show_log_output): 254 | """Integration test for retrieval of roster with player info for chosen team by team ID and by date for chosen Yahoo 255 | fantasy league. 256 | 257 | Note: 258 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_team_roster_player_info_by_date`. 259 | 260 | """ 261 | new_data_dir = data_dir / str(season) / f"{game_id}.l.{league_id}" / str(chosen_date) / "rosters" 262 | query_result_data = yahoo_data.save( 263 | f"{team_id}-{team_name}-roster-player_info_by_date", 264 | yahoo_query.get_team_roster_player_info_by_date, 265 | params={"team_id": team_id, "chosen_date": chosen_date}, 266 | new_data_dir=new_data_dir 267 | ) 268 | if show_log_output: 269 | logger.info(prettify_data(query_result_data)) 270 | 271 | loaded_result_data = yahoo_data.load( 272 | f"{team_id}-{team_name}-roster-player_info_by_date", 273 | new_data_dir=new_data_dir, 274 | all_output_as_json_str=yahoo_query.all_output_as_json_str 275 | ) 276 | if show_log_output: 277 | logger.info(prettify_data(loaded_result_data)) 278 | 279 | assert query_result_data == loaded_result_data 280 | 281 | 282 | @pytest.mark.integration 283 | def test_get_team_roster_player_stats(yahoo_query, yahoo_data, data_dir, season, game_id, league_id, team_id, 284 | team_name, show_log_output): 285 | """Integration test for retrieval of roster with player info for chosen team by team ID and for chosen season for 286 | chosen Yahoo fantasy league. 287 | 288 | Note: 289 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_team_roster_player_stats`. 290 | 291 | """ 292 | new_data_dir = data_dir / str(season) / f"{game_id}.l.{league_id}" / "rosters" 293 | query_result_data = yahoo_data.save( 294 | f"{team_id}-{team_name}-roster-player_stats", 295 | yahoo_query.get_team_roster_player_stats, 296 | params={"team_id": team_id}, 297 | new_data_dir=new_data_dir 298 | ) 299 | if show_log_output: 300 | logger.info(prettify_data(query_result_data)) 301 | 302 | loaded_result_data = yahoo_data.load( 303 | f"{team_id}-{team_name}-roster-player_stats", 304 | new_data_dir=new_data_dir, 305 | all_output_as_json_str=yahoo_query.all_output_as_json_str 306 | ) 307 | if show_log_output: 308 | logger.info(prettify_data(loaded_result_data)) 309 | 310 | assert query_result_data == loaded_result_data 311 | 312 | 313 | @pytest.mark.integration 314 | def test_get_team_roster_player_stats_by_week(yahoo_query, yahoo_data, data_dir, season, chosen_week, game_id, 315 | league_id, team_id, team_name, show_log_output): 316 | """Integration test for retrieval of roster with player info for chosen team by team ID and by week for chosen Yahoo 317 | fantasy league. 318 | 319 | Note: 320 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_team_roster_player_stats_by_week`. 321 | 322 | """ 323 | new_data_dir = data_dir / str(season) / f"{game_id}.l.{league_id}" / f"week_{chosen_week}" / "rosters" 324 | query_result_data = yahoo_data.save( 325 | f"{team_id}-{team_name}-roster-player_stats_by_week", 326 | yahoo_query.get_team_roster_player_stats_by_week, 327 | params={"team_id": team_id, "chosen_week": chosen_week}, 328 | new_data_dir=new_data_dir 329 | ) 330 | if show_log_output: 331 | logger.info(prettify_data(query_result_data)) 332 | 333 | loaded_result_data = yahoo_data.load( 334 | f"{team_id}-{team_name}-roster-player_stats_by_week", 335 | new_data_dir=new_data_dir, 336 | all_output_as_json_str=yahoo_query.all_output_as_json_str 337 | ) 338 | if show_log_output: 339 | logger.info(prettify_data(loaded_result_data)) 340 | 341 | assert query_result_data == loaded_result_data 342 | 343 | 344 | @pytest.mark.integration 345 | def test_get_team_draft_results(yahoo_query, yahoo_data, data_dir, season, game_id, league_id, team_id, team_name, 346 | show_log_output): 347 | """Integration test for retrieval of draft results for chosen team by team ID for chosen Yahoo fantasy league. 348 | 349 | Note: 350 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_team_draft_results`. 351 | 352 | """ 353 | new_data_dir = data_dir / str(season) / f"{game_id}.l.{league_id}" / "teams" / f"{team_id}-{team_name}" 354 | query_result_data = yahoo_data.save( 355 | f"{team_id}-{team_name}-draft_results", 356 | yahoo_query.get_team_draft_results, 357 | params={"team_id": team_id}, 358 | new_data_dir=new_data_dir 359 | ) 360 | if show_log_output: 361 | logger.info(prettify_data(query_result_data)) 362 | 363 | loaded_result_data = yahoo_data.load( 364 | f"{team_id}-{team_name}-draft_results", 365 | new_data_dir=new_data_dir, 366 | all_output_as_json_str=yahoo_query.all_output_as_json_str 367 | ) 368 | if show_log_output: 369 | logger.info(prettify_data(loaded_result_data)) 370 | 371 | assert query_result_data == loaded_result_data 372 | 373 | 374 | @pytest.mark.integration 375 | def test_get_team_matchups(yahoo_query, yahoo_data, data_dir, season, game_id, league_id, team_id, team_name, 376 | show_log_output): 377 | """Integration test for retrieval of matchups for chosen team by team ID for chosen Yahoo fantasy league. 378 | 379 | Note: 380 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_team_matchups`. 381 | 382 | """ 383 | new_data_dir = data_dir / str(season) / f"{game_id}.l.{league_id}" / "teams" / f"{team_id}-{team_name}" 384 | query_result_data = yahoo_data.save( 385 | f"{team_id}-{team_name}-matchups", 386 | yahoo_query.get_team_matchups, 387 | params={"team_id": team_id}, 388 | new_data_dir=new_data_dir 389 | ) 390 | if show_log_output: 391 | logger.info(prettify_data(query_result_data)) 392 | 393 | loaded_result_data = yahoo_data.load( 394 | f"{team_id}-{team_name}-matchups", 395 | new_data_dir=new_data_dir, 396 | all_output_as_json_str=yahoo_query.all_output_as_json_str 397 | ) 398 | if show_log_output: 399 | logger.info(prettify_data(loaded_result_data)) 400 | 401 | assert query_result_data == loaded_result_data 402 | -------------------------------------------------------------------------------- /test/integration/test_api_user_data.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Pytest integration tests for Yahoo Fantasy Sports API user data. 3 | 4 | Note: 5 | Tests saving and loading all Yahoo Fantasy Sports API user data. 6 | 7 | Attributes: 8 | logger (Logger): Game data integration tests logger. 9 | 10 | """ 11 | __author__ = "Wren J. R. (uberfastman)" 12 | __email__ = "uberfastman@uberfastman.dev" 13 | 14 | import logging 15 | import warnings 16 | 17 | import pytest 18 | 19 | from yfpy.logger import get_logger 20 | from yfpy.models import User 21 | from yfpy.utils import prettify_data 22 | 23 | logger = get_logger(__name__) 24 | 25 | # Suppress YahooFantasySportsQuery debug logging 26 | logging.getLogger("yfpy.query").setLevel(level=logging.INFO) 27 | 28 | # Ignore resource warnings from unittest module 29 | warnings.simplefilter("ignore", ResourceWarning) 30 | 31 | 32 | @pytest.mark.integration 33 | def test_get_current_user(yahoo_query, yahoo_data, data_dir, season, game_id, show_log_output): 34 | """Integration test for retrieval of metadata for current logged-in Yahoo user. 35 | 36 | Note: 37 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_current_user`. 38 | 39 | """ 40 | new_data_dir = data_dir 41 | query_result_data = yahoo_data.save("user", yahoo_query.get_current_user, new_data_dir=new_data_dir) 42 | if show_log_output: 43 | logger.info(prettify_data(query_result_data)) 44 | 45 | loaded_result_data = yahoo_data.load( 46 | "user", 47 | User, 48 | new_data_dir=new_data_dir, 49 | all_output_as_json_str=yahoo_query.all_output_as_json_str 50 | ) 51 | if show_log_output: 52 | logger.info(prettify_data(loaded_result_data)) 53 | 54 | assert query_result_data == loaded_result_data 55 | 56 | 57 | @pytest.mark.integration 58 | def test_get_user_games(yahoo_query, yahoo_data, data_dir, season, game_id, show_log_output): 59 | """Integration test for retrieval of game history for current logged-in Yahoo user. 60 | 61 | Note: 62 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_user_games`. 63 | 64 | """ 65 | new_data_dir = data_dir 66 | query_result_data = yahoo_data.save( 67 | "user-games", 68 | yahoo_query.get_user_games, 69 | new_data_dir=new_data_dir 70 | ) 71 | if show_log_output: 72 | logger.info(prettify_data(query_result_data)) 73 | 74 | loaded_result_data = yahoo_data.load( 75 | "user-games", 76 | new_data_dir=new_data_dir, 77 | all_output_as_json_str=yahoo_query.all_output_as_json_str 78 | ) 79 | if show_log_output: 80 | logger.info(prettify_data(loaded_result_data)) 81 | 82 | assert query_result_data == loaded_result_data 83 | 84 | 85 | @pytest.mark.skip( 86 | reason="Skipping get_user_leagues_by_game_key: current logged-in user must have leagues from test season/year." 87 | ) 88 | @pytest.mark.integration 89 | def test_get_user_leagues_by_game_id(yahoo_query, yahoo_data, data_dir, season, game_id, show_log_output): 90 | """Integration test for retrieval of league history by game ID for current logged-in Yahoo user. 91 | 92 | Note: 93 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_user_leagues_by_game_key`. 94 | 95 | """ 96 | new_data_dir = data_dir 97 | query_result_data = yahoo_data.save( 98 | "user-leagues", 99 | yahoo_query.get_user_leagues_by_game_key, 100 | params={"game_key": game_id}, 101 | new_data_dir=new_data_dir 102 | ) 103 | 104 | if show_log_output: 105 | logger.info(prettify_data(query_result_data)) 106 | 107 | loaded_result_data = yahoo_data.load( 108 | "user-leagues", 109 | new_data_dir=new_data_dir, 110 | all_output_as_json_str=yahoo_query.all_output_as_json_str 111 | ) 112 | if show_log_output: 113 | logger.info(prettify_data(loaded_result_data)) 114 | 115 | assert query_result_data == loaded_result_data 116 | 117 | 118 | @pytest.mark.integration 119 | def test_get_user_teams(yahoo_query, yahoo_data, data_dir, season, game_id, show_log_output): 120 | """Integration test for retrieval of teams for the current game for the current logged-in Yahoo user. 121 | 122 | Note: 123 | Tests :func:`~yfpy.query.YahooFantasySportsQuery.get_user_teams`. 124 | 125 | """ 126 | """Retrieve teams for all leagues for current logged-in user for current game. 127 | """ 128 | new_data_dir = data_dir 129 | query_result_data = yahoo_data.save( 130 | "user-teams", 131 | yahoo_query.get_user_teams, 132 | new_data_dir=new_data_dir 133 | ) 134 | if show_log_output: 135 | logger.info(prettify_data(query_result_data)) 136 | 137 | loaded_result_data = yahoo_data.load( 138 | "user-teams", 139 | new_data_dir=new_data_dir, 140 | all_output_as_json_str=yahoo_query.all_output_as_json_str 141 | ) 142 | if show_log_output: 143 | logger.info(prettify_data(loaded_result_data)) 144 | 145 | assert query_result_data == loaded_result_data 146 | -------------------------------------------------------------------------------- /yfpy/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = "Wren J. R. (uberfastman)" 2 | __email__ = "uberfastman@uberfastman.dev" 3 | 4 | from yfpy.data import Data 5 | from yfpy.exceptions import YahooFantasySportsException, YahooFantasySportsDataNotFound 6 | from yfpy.logger import get_logger 7 | from yfpy.models import ( 8 | User, 9 | Game, 10 | GameWeek, 11 | PositionType, 12 | League, 13 | Team, 14 | DraftResult, 15 | Standings, 16 | Transaction, 17 | Pick, 18 | Manager, 19 | Roster, 20 | RosterAdds, 21 | TeamLogo, 22 | TeamPoints, 23 | TeamProjectedPoints, 24 | TeamStandings, 25 | DivisionalOutcomeTotals, 26 | OutcomeTotals, 27 | Streak, 28 | Scoreboard, 29 | Settings, 30 | Division, 31 | RosterPosition, 32 | StatCategories, 33 | Group, 34 | StatModifiers, 35 | Stat, 36 | StatPositionType, 37 | Bonus, 38 | Matchup, 39 | MatchupGrade, 40 | Player, 41 | ByeWeeks, 42 | DraftAnalysis, 43 | Headshot, 44 | Name, 45 | Ownership, 46 | PercentOwned, 47 | PlayerAdvancedStats, 48 | PlayerPoints, 49 | PlayerStats, 50 | SelectedPosition, 51 | TransactionData 52 | ) 53 | from yfpy.query import YahooFantasySportsQuery 54 | -------------------------------------------------------------------------------- /yfpy/data.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """YFPY module for retrieving, saving, and loading all Yahoo Fantasy Sports data. 3 | 4 | This module is designed to allow for easy data management, such that both retrieving and managing Yahoo Fantasy Sports 5 | data can be done in one place without the need to manually run the queries and manage data saved to JSON files. 6 | 7 | Example: 8 | The Data module can be used as follows:: 9 | 10 | yahoo_query = YahooFantasySportsQuery( 11 | "", 12 | "", 13 | game_id="", 14 | yahoo_consumer_key=os.environ.get("YFPY_CONSUMER_KEY"), 15 | yahoo_consumer_secret=os.environ.get("YFPY_CONSUMER_SECRET"), 16 | all_output_as_json_str=False, 17 | browser_callback=True, 18 | offline=False 19 | ) 20 | 21 | data_directory = Path(__file__).parent / "output" 22 | data = Data(data_dir) 23 | data.save("file_name", yahoo_query.get_all_yahoo_fantasy_game_keys) 24 | data.load("file_name") 25 | 26 | Attributes: 27 | logger (Logger): Module level logger for usage and debugging. 28 | 29 | """ 30 | __author__ = "Wren J. R. (uberfastman)" 31 | __email__ = "uberfastman@uberfastman.dev" 32 | 33 | import inspect 34 | import json 35 | from pathlib import Path, PosixPath 36 | from typing import Any, Callable, Dict, List, Type, TypeVar, Union 37 | 38 | from stringcase import snakecase 39 | 40 | from yfpy.logger import get_logger 41 | from yfpy.models import YahooFantasyObject 42 | from yfpy.query import YahooFantasySportsQuery 43 | from yfpy.utils import jsonify_data, jsonify_data_to_file, unpack_data 44 | 45 | logger = get_logger(__name__) 46 | 47 | 48 | class Data(object): 49 | """YFPY Data object for Yahoo Fantasy Sports data retrieval, saving, and loading data as JSON. 50 | """ 51 | 52 | YFO = TypeVar("YFO", bound=YahooFantasyObject) 53 | 54 | def __init__(self, data_dir: Union[Path, str], save_data: bool = False, dev_offline: bool = False): 55 | """Instantiate data object to retrieve, save, and load Yahoo Fantasy Sports data. 56 | 57 | Args: 58 | data_dir (Path | str): Directory path where data will be saved/loaded. 59 | save_data (bool, optional): Boolean determining whether data is saved after retrieval from the Yahoo FF API. 60 | dev_offline (bool, optional): Boolean for offline development (requires a prior online run with 61 | save_data = True). 62 | 63 | """ 64 | self.data_dir: Path = data_dir if isinstance(data_dir, PosixPath) else Path(data_dir) 65 | self.save_data: bool = save_data 66 | self.dev_offline: bool = dev_offline 67 | 68 | def update_data_dir(self, new_save_dir: Union[Path, str]) -> None: 69 | """Modify the data storage directory if it needs to be updated. 70 | 71 | Args: 72 | new_save_dir (str | Path): Full path to new desired directory where data will be saved/loaded. 73 | 74 | Returns: 75 | None 76 | 77 | """ 78 | self.data_dir: Path = new_save_dir if isinstance(new_save_dir, PosixPath) else Path(new_save_dir) 79 | 80 | @staticmethod 81 | def fetch(yf_query: Callable, 82 | params: Union[Dict[str, str], None] = None) -> Union[str, YFO, List[YFO], Dict[str, YFO]]: 83 | """Run query to retrieve Yahoo Fantasy Sports data. 84 | 85 | Args: 86 | yf_query (Callable of YahooFantasySportsQuery): Chosen yfpy query method to run. 87 | params (dict of str: str, optional): Dictionary of parameters to be passed to chosen yfpy query function. 88 | 89 | Returns: 90 | object: Data retrieved by the yfpy query. 91 | 92 | """ 93 | if params: 94 | return yf_query(**params) 95 | else: 96 | return yf_query() 97 | 98 | def save(self, file_name: str, yf_query: Callable, params: Union[Dict[str, Any], None] = None, 99 | new_data_dir: Union[Path, str, None] = None) -> Union[str, YFO, List[YFO], Dict[str, YFO]]: 100 | """Retrieve and save Yahoo Fantasy Sports data locally. 101 | 102 | Args: 103 | file_name (str): Name of file to which data will be saved. 104 | yf_query (Callable of YahooFantasySportsQuery): Chosen yfpy query method to run. 105 | params (dict of str: str, optional): Dictionary of parameters to be passed to chosen yfpy query function. 106 | new_data_dir (str | Path, optional): Full path to new desired directory to which data will be saved. 107 | 108 | Returns: 109 | object: Data retrieved by the yfpy query. 110 | 111 | """ 112 | # change data save directory 113 | if new_data_dir: 114 | new_data_dir = new_data_dir if isinstance(new_data_dir, PosixPath) else Path(new_data_dir) 115 | logger.debug(f"Data directory changed from {self.data_dir} to {new_data_dir}.") 116 | self.update_data_dir(new_data_dir) 117 | 118 | # create full directory path if any directories in it do not already exist 119 | if not self.data_dir.exists(): 120 | self.data_dir.mkdir(parents=True, exist_ok=True) 121 | 122 | # check if parent YahooFantasySportsQuery.all_output_as_json_str = True, unset it for data saving, then reset it 123 | all_output_as_json = False 124 | # noinspection PyUnresolvedReferences 125 | if yf_query.__self__.all_output_as_json_str: 126 | if inspect.ismethod(yf_query): 127 | # noinspection PyUnresolvedReferences 128 | for cls in inspect.getmro(yf_query.__self__.__class__): 129 | if yf_query.__name__ in cls.__dict__: 130 | # noinspection PyUnresolvedReferences 131 | yf_query.__self__.all_output_as_json_str = False 132 | all_output_as_json = True 133 | 134 | # run the actual yfpy query and retrieve the query results 135 | data = self.fetch(yf_query, params) 136 | 137 | # save the retrieved data locally 138 | saved_data_file_path = self.data_dir / f"{file_name}.json" 139 | with open(saved_data_file_path, "w", encoding="utf-8") as data_file: 140 | if isinstance(data, list): 141 | # unflatten list of object values to list of single-key dictionaries with object values if top level of 142 | # data to be saved is a list 143 | unnested_data = [{snakecase(el.__class__.__name__): el} for el in data] 144 | else: 145 | unnested_data = data 146 | 147 | jsonify_data_to_file(unnested_data, data_file) 148 | logger.debug(f"Data saved locally to: {saved_data_file_path}") 149 | 150 | # reset parent YahooFantasySportsQuery all_output_as_json_str = True and re-run query for json string return 151 | if all_output_as_json: 152 | # noinspection PyUnresolvedReferences 153 | yf_query.__self__.all_output_as_json_str = True 154 | # data = self.get(yf_query, params) 155 | data = jsonify_data(data) 156 | 157 | return data 158 | 159 | def load(self, file_name: str, data_type_class: Type[YahooFantasyObject] = None, 160 | new_data_dir: Union[Path, str, None] = None, 161 | all_output_as_json_str: bool = False) -> Union[str, YFO, List[YFO], Dict[str, YFO]]: 162 | """Load Yahoo Fantasy Sports data already stored locally. 163 | 164 | Note: 165 | This method will fail if the `save` method has not been called previously. 166 | 167 | Args: 168 | file_name (str): Name of file from which data will be loaded. 169 | data_type_class (Type[YahooFantasyObject], optional): YFPY models.py class for data casting. 170 | new_data_dir (str | Path, optional): Full path to new desired directory from which data will be loaded. 171 | all_output_as_json_str (bool): Boolean indicating if the output has been requested as a raw JSON string. 172 | 173 | Returns: 174 | object: Data loaded from the selected JSON file. 175 | 176 | """ 177 | # change data load directory 178 | if new_data_dir: 179 | new_data_dir = new_data_dir if isinstance(new_data_dir, PosixPath) else Path(new_data_dir) 180 | self.update_data_dir(new_data_dir) 181 | 182 | # load selected data file 183 | saved_data_file_path = self.data_dir / f"{file_name}.json" 184 | if saved_data_file_path.exists(): 185 | with open(saved_data_file_path, "r", encoding="utf-8") as data_file: 186 | unpacked = unpack_data(json.load(data_file), YahooFantasyObject) 187 | data = data_type_class(unpacked) if data_type_class else unpacked 188 | 189 | if isinstance(data, list): 190 | # flatten list of single-key dictionaries with object values to list of object values if top level 191 | # of loaded data is a list 192 | data_element_key = list(data[0].keys())[0] 193 | data = [el[data_element_key] for el in data] 194 | 195 | logger.debug(f"Data loaded locally from: {saved_data_file_path}") 196 | else: 197 | raise FileNotFoundError(f"File {saved_data_file_path} does not exist. Cannot load data locally without " 198 | f"having previously saved data.") 199 | 200 | if all_output_as_json_str: 201 | return jsonify_data(data) 202 | else: 203 | return data 204 | 205 | def retrieve(self, file_name: str, yf_query: Callable, params: Union[Dict[str, str], None] = None, 206 | data_type_class: Type[YahooFantasyObject] = None, 207 | new_data_dir: Union[Path, str, None] = None) -> Union[str, YFO, List[YFO], Dict[str, YFO]]: 208 | """Fetch data from the web or load it locally (combination of the save and load methods). 209 | 210 | Args: 211 | file_name (str): Name of file to/from which data will be saved/loaded. 212 | yf_query (Callable of YahooFantasySportsQuery): Chosen yfpy query method to run. 213 | params (dict of str: str, optional): Dictionary of parameters to be passed to chosen yfpy query function. 214 | data_type_class (Type[YahooFantasyObject], optional): YFPY models.py class for data casting. 215 | new_data_dir (str | Path, optional): Full path to new desired directory to/from which data will be 216 | saved/loaded. 217 | 218 | Returns: 219 | object: Data retrieved by the yfpy query OR loaded from the selected JSON file. 220 | 221 | """ 222 | if self.dev_offline: 223 | return self.load(file_name, data_type_class, new_data_dir) 224 | else: 225 | if self.save_data: 226 | return self.save(file_name, yf_query, params, new_data_dir) 227 | else: 228 | return self.fetch(yf_query, params) 229 | -------------------------------------------------------------------------------- /yfpy/exceptions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """YFPY module for throwing custom exceptions. 3 | 4 | Attributes: 5 | logger (Logger): Module level logger for usage and debugging. 6 | 7 | """ 8 | __author__ = "Wren J. R. (uberfastman)" 9 | __email__ = "uberfastman@uberfastman.dev" 10 | 11 | from yfpy.logger import get_logger 12 | 13 | logger = get_logger(__name__) 14 | 15 | 16 | class YahooFantasySportsException(Exception): 17 | """Base YFPY exception class for Yahoo Fantasy Sports API exceptions. 18 | """ 19 | def __init__(self, message: str, payload: str = None, url: str = None): 20 | """Instantiate YFPY exception. 21 | 22 | Args: 23 | message (str): Human readable string describing the exception. 24 | payload (str, optional): The API exception error payload. 25 | url (str, optional): Yahoo Fantasy Sports REST API URL. 26 | 27 | Attributes: 28 | message (str): Human readable string describing the exception. 29 | payload (str): The API exception error payload. 30 | url (str): Yahoo Fantasy Sports REST API URL. 31 | 32 | """ 33 | self.message: str = message 34 | self.payload: str = payload 35 | self.url: str = url 36 | 37 | def __str__(self): 38 | return str(self.message) 39 | 40 | 41 | class YahooFantasySportsDataNotFound(YahooFantasySportsException): 42 | """YFPY exception when no data was retrieved from the Yahoo Fantasy Sports REST API.""" 43 | -------------------------------------------------------------------------------- /yfpy/logger.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """YFPY module for configuring and formatting the custom logger. 3 | """ 4 | __author__ = "Wren J. R. (uberfastman)" 5 | __email__ = "uberfastman@uberfastman.dev" 6 | 7 | from logging import getLogger, Logger, Formatter, StreamHandler, INFO 8 | 9 | 10 | def get_logger(name: str, level: int = INFO) -> Logger: 11 | """Get custom YFPY logger object. 12 | 13 | Args: 14 | name (str): The module name for the logger. 15 | level (int): The log level for the logger. Default level set to INFO. 16 | 17 | Returns: 18 | Logger: A Python Logger object with custom configuration and formatting. 19 | 20 | """ 21 | logger = getLogger(name) 22 | if len(logger.handlers) > 0: 23 | logger.handlers = [] 24 | if level: 25 | logger.setLevel(level) 26 | 27 | sh = StreamHandler() 28 | if level: 29 | sh.setLevel(level) 30 | 31 | formatter = Formatter( 32 | fmt="%(asctime)s.%(msecs)03d - %(levelname)s - %(filename)s - %(name)s:%(lineno)d - %(message)s", 33 | datefmt="%Y-%m-%d %H:%M:%S" 34 | ) 35 | sh.setFormatter(formatter) 36 | 37 | logger.addHandler(sh) 38 | 39 | return logger 40 | -------------------------------------------------------------------------------- /yfpy/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """YFPY module for managing complex JSON data structures. 3 | 4 | Attributes: 5 | logger (Logger): Module level logger for usage and debugging. 6 | 7 | """ 8 | __author__ = "Wren J. R. (uberfastman)" 9 | __email__ = "uberfastman@uberfastman.dev" 10 | 11 | import json 12 | import re 13 | from collections import ChainMap, OrderedDict 14 | from typing import Any, Dict, IO, List, Type, Union 15 | from time import sleep 16 | 17 | import stringcase 18 | 19 | from yfpy.logger import get_logger 20 | 21 | logger = get_logger(__name__) 22 | 23 | yahoo_fantasy_sports_game_codes = ["nfl", "nhl", "mlb", "nba"] 24 | 25 | 26 | def retrieve_game_code_from_user() -> str: 27 | """Recursive function to retrieve required Yahoo Fantasy Sports game code from user input. 28 | 29 | Returns: 30 | str: Yahoo Fantasy Sports game code ("nfl", "nhl", "mlb", or "nba"). 31 | 32 | """ 33 | game_code = input( 34 | "YFPY requires the use of a Yahoo Fantasy Sports game code in order to select the correct sport. Please enter " 35 | "NFL, NHL, MLB, or NBA (case does not matter) here -> " 36 | ) 37 | game_code = game_code.strip().lower() 38 | if game_code in yahoo_fantasy_sports_game_codes: 39 | return game_code 40 | else: 41 | logger.warning(f"Selection \"{game_code}\" is not a valid Yahoo Fantasy Sports game code. Please try again.") 42 | sleep(0.25) 43 | return retrieve_game_code_from_user() 44 | 45 | 46 | def complex_json_handler(obj: Any) -> Any: 47 | """Custom handler to allow custom YFPY objects to be serialized into JSON. 48 | 49 | Args: 50 | obj (Any): Unserializable Python object to be serialized into JSON. 51 | 52 | Returns: 53 | Any: Serializable version of the Python object. 54 | 55 | """ 56 | if hasattr(obj, "serialized"): 57 | return obj.serialized() 58 | else: 59 | try: 60 | return str(obj, "utf-8") 61 | except TypeError: 62 | raise TypeError('Object of type %s with value of %s is not JSON serializable' % (type(obj), repr(obj))) 63 | 64 | 65 | def jsonify_data(data: object) -> str: 66 | """Function to serialize a YahooFantasyObject to a JSON string. 67 | 68 | Args: 69 | data (object): YahooFantasyObject to be serialized to a JSON string. 70 | 71 | Returns: 72 | str: JSON string serialized from YahooFantasyObject. 73 | 74 | """ 75 | return json.dumps(data, indent=2, ensure_ascii=False, default=complex_json_handler) 76 | 77 | 78 | def jsonify_data_to_file(data: object, data_file: IO[str]) -> None: 79 | """Function to serialize a YahooFantasyObject to JSON and output it to a file. 80 | 81 | Args: 82 | data (object): YahooFantasyObject to be serialized to JSON and output to a file. 83 | data_file (IO[str]): Target filestream for writing the data. 84 | 85 | """ 86 | json.dump(data, data_file, indent=2, ensure_ascii=False, default=complex_json_handler) 87 | 88 | 89 | def prettify_data(data: object) -> str: 90 | """Function to return pretty formatted JSON strings for easily readable output from objects. 91 | 92 | Args: 93 | data (object): Data object to be printed as an easily readable JSON string. 94 | 95 | Returns: 96 | str: JSON string that has been formatted with indents (two spaces). 97 | 98 | """ 99 | return f"\n{jsonify_data(data)}\n" 100 | 101 | 102 | # noinspection PyTypeChecker 103 | def unpack_data(json_obj: Any, parent_class: Type = None) -> Any: 104 | """Recursive function to parse, clean, and assign custom data types to retrieved Yahoo Fantasy Sports data. 105 | 106 | Args: 107 | json_obj (Any): JSON object for parsing (can be a dictionary, list, or primitive). 108 | parent_class (Type): Parent class type used to extract custom subclass type options for casting. 109 | 110 | Returns: 111 | Any: Recursively returns JSON objects until data is completely parsed, cleaned, and typed (where applicable). 112 | 113 | """ 114 | # extract subclasses from parent class for typing 115 | subclasses = {} 116 | if parent_class: 117 | subclasses = {stringcase.snakecase(cls.__name__): cls for cls in parent_class.__subclasses__()} 118 | 119 | # discard empty lists and dictionaries and include when json value = 0 120 | if json_obj == 0 or json_obj: 121 | # handle lists 122 | if isinstance(json_obj, list): 123 | json_obj = [obj for obj in json_obj if (obj == 0 or obj)] 124 | 125 | if len(json_obj) == 1: 126 | return unpack_data(json_obj[0], parent_class) 127 | else: 128 | # flatten list of dicts if any objects in the list are dicts 129 | if any(isinstance(obj, dict) for obj in json_obj): 130 | return flatten_json_dict_list(json_obj, parent_class) 131 | 132 | return [unpack_data(obj, parent_class) for obj in json_obj if (obj == 0 or obj)] 133 | 134 | # handle dictionaries 135 | elif isinstance(json_obj, dict): 136 | 137 | # eliminate odd single-key Yahoo dicts with key = "0" and value = 138 | if "0" in json_obj.keys() and "1" not in json_obj.keys(): 139 | if len(json_obj.keys()) == 1: 140 | return unpack_data(json_obj.get("0"), parent_class) 141 | else: 142 | if isinstance(json_obj.get("0"), dict): 143 | json_obj.update(json_obj.pop("0")) 144 | 145 | # eliminate data obj counts (except in player_position dicts, which have position counts in league settings) 146 | if "count" in json_obj.keys() and "position" in json_obj.keys(): 147 | # assign/cast data type where applicable 148 | # TODO: figure out how to do this without explicit object type keys 149 | return get_type( 150 | {k: unpack_data(v, parent_class) for k, v in json_obj.items()}, 151 | parent_class, 152 | subclasses 153 | ) 154 | else: 155 | # assign/cast data type where applicable 156 | # TODO: figure out how to do this without explicit object type keys 157 | json_obj = get_type( 158 | {k: unpack_data(v, parent_class) for k, v in json_obj.items() if k != "count"}, 159 | parent_class, 160 | subclasses 161 | ) 162 | 163 | # flatten dicts with keys "0", "1",..., "n" to a list of objects 164 | if "0" in json_obj.keys() and "1" in json_obj.keys(): 165 | json_obj = flatten_to_list(json_obj) 166 | # TODO: figure out how to do this without breaking the above unpacking using explicit type keys 167 | # else: 168 | # # flatten dicts with redundant keys to a list of objects 169 | # if len(json_obj.keys()) == 1 and len(json_obj.values()) == 1: 170 | # key = list(json_obj.keys())[0] 171 | # value = list(json_obj.values())[0] 172 | # json_obj = value 173 | 174 | return json_obj 175 | else: 176 | return convert_strings_to_numeric_equivalents(json_obj) 177 | 178 | 179 | def convert_strings_to_numeric_equivalents(json_obj: Any) -> Union[int, float, Any]: 180 | """Convert JSON strings with integer or float numeric representations to their respective integer or float values. 181 | 182 | Args: 183 | json_obj (Any): JSON object (typically a dictionary or list, but can also be a primitive). 184 | 185 | Returns: 186 | int | float | Any: The numeric representation of any JSON strings that can be represented as integers or floats, 187 | else the original JSON object. 188 | 189 | """ 190 | if isinstance(json_obj, str): 191 | 192 | if len(json_obj) > 1 and str.startswith(json_obj, "0"): 193 | return json_obj 194 | else: 195 | if str.isdigit(json_obj): 196 | return int(json_obj) 197 | elif str.isdigit(re.sub("[-]", "", re.sub("[.]", "", json_obj, count=1), count=1)): 198 | return float(json_obj) 199 | else: 200 | return json_obj 201 | else: 202 | return json_obj 203 | 204 | 205 | def get_type(json_obj_dict: Dict[str, Any], parent_class: Type, subclasses: Dict[str, Type]) -> Dict[str, Any]: 206 | """Cast JSON object to custom subclass type extracted from parent class. 207 | 208 | Args: 209 | json_obj_dict (dict of str: Any): JSON dictionary with strings of data type as keys and JSON objects as values. 210 | parent_class (Type): Parent class from which to derive subclasses for casting. 211 | subclasses (dict of str: Type): Dictionary of subclasses with strings that match the json dict keys as keys 212 | and classes for casting as values. 213 | 214 | Returns: 215 | object: A Python object (representing the original JSON object) that has been cast to the specified type. 216 | 217 | """ 218 | for k, v in json_obj_dict.items(): 219 | # check if key is in the provided subclasses' dict, that the object isn't already cast 220 | if k in subclasses.keys() and isinstance(v, dict) and not isinstance(v, subclasses.get(k)): 221 | json_obj_dict[k] = subclasses[k](unpack_data(v, parent_class)) 222 | return json_obj_dict 223 | 224 | 225 | def flatten_json_dict_list(json_obj_dict_list: List[Dict[str, Any]], parent_class: Type) -> Any: 226 | """Recursive function to flatten JSON lists containing all disparate JSON dictionaries with no overlapping keys. 227 | 228 | Args: 229 | json_obj_dict_list (list[dict[str, Any]]): List of JSON dictionaries. 230 | parent_class (Type): Parent class type used to extract custom subclass type options. 231 | 232 | Returns: 233 | dict | list: Returns a dictionary if the list was flattened, else a cleaned list if no flattening was needed. 234 | 235 | """ 236 | # filter out empty lists and dicts but include when value = 0 237 | json_obj_dict_list = [obj for obj in json_obj_dict_list if (obj == 0 or obj)] 238 | item_keys = [] 239 | ndx = 0 240 | for item in json_obj_dict_list: 241 | if isinstance(item, list): 242 | flattened_item = flatten_json_dict_list(item, parent_class) 243 | json_obj_dict_list[ndx] = flattened_item 244 | item_keys.extend(list(flattened_item.keys())) 245 | else: 246 | item_keys.extend(list(item.keys())) 247 | ndx += 1 248 | 249 | if len(item_keys) == len(set(item_keys)): 250 | agg_dict = {} 251 | for dict_item in json_obj_dict_list: 252 | agg_dict.update(dict_item) 253 | 254 | return unpack_data(agg_dict, parent_class) 255 | else: 256 | return [unpack_data(obj, parent_class) for obj in json_obj_dict_list if (obj == 0 or obj)] 257 | 258 | 259 | def flatten_to_list(json_obj: Any) -> Any: 260 | """Function to flatten JSON dictionaries with unnecessary keys to a list of objects. 261 | 262 | Args: 263 | json_obj (Any): JSON object (typically a dictionary or list, but can also be a primitive). 264 | 265 | Returns: 266 | list: A list made from a flattened dictionary if json_obj was a dictionary, the original list if json_obj was a 267 | list, or the original value if json_obj was a primitive. 268 | 269 | """ 270 | if isinstance(json_obj, dict): 271 | out = [] 272 | for k, v in json_obj.items(): 273 | out.append(v) 274 | return out 275 | else: 276 | return json_obj 277 | 278 | 279 | def flatten_to_objects(json_obj: Any) -> Any: 280 | """Function to flatten a JSON dictionary (or a JSON dictionary in a list) to a dictionary of cast objects. 281 | 282 | Args: 283 | json_obj (Any): JSON object (typically a dictionary or list, but can also be a primitive). 284 | 285 | Returns: 286 | dict | list | int | float | str | bool: JSON dictionary/list/primitive with contents cast to Python objects. 287 | 288 | """ 289 | if isinstance(json_obj, dict): 290 | return dict_to_list(json_obj) 291 | elif isinstance(json_obj, list): 292 | if isinstance(json_obj[0], dict): 293 | return dict_to_list(json_obj[0]) 294 | else: 295 | return json_obj 296 | 297 | 298 | def dict_to_list(json_dict: Dict[str, Any]) -> Any: 299 | """Function to convert a JSON dictionary to a list. 300 | 301 | Args: 302 | json_dict (dict[str, Any]): JSON dictionary. 303 | 304 | Returns: 305 | list: A list derived from a JSON dictionary, or the original dictionary if it does not contain dictionaries as 306 | values. 307 | 308 | """ 309 | first_key = list(json_dict.keys())[0] 310 | if isinstance(json_dict.get(first_key), dict): 311 | first_val_key = list(json_dict.get(first_key).keys())[0] 312 | if first_key[:-1] == first_val_key: 313 | out = [] 314 | for k, v in json_dict.items(): 315 | out.append(v.get(first_val_key)) 316 | return out 317 | return json_dict 318 | 319 | 320 | def reorganize_json_dict(json_dict: Dict[str, Any], obj_key: str, val_to_key: str) -> Dict[str, Any]: 321 | """Function to reorganize a JSON dictionary of dictionaries. 322 | 323 | The reorganized JSON dictionary is an ordered dictionary sorted by a specific attribute of the value dictionaries. 324 | 325 | Args: 326 | json_dict (dict of str: Any): JSON dictionary. 327 | obj_key (str): Key to access the dictionaries contained in json_dict. 328 | val_to_key (str): Key used to sort the dictionaries contained in json_dict. 329 | 330 | Returns: 331 | dict[str, Any]: An ordered dictionary of dictionaries sorted by val_to_key. 332 | 333 | """ 334 | out = {} 335 | for k, v in json_dict.items(): 336 | out[str(getattr(v.get(obj_key), val_to_key))] = v.get(obj_key) 337 | 338 | return OrderedDict( 339 | (str(k), out[str(k)]) for k in sorted( 340 | [int(k_v) if isinstance(k_v, int) else k_v for k_v in out.keys()])) 341 | 342 | 343 | def reformat_json_list(json_obj: Any) -> Any: 344 | """Function to clean and reformat JSON lists to eliminate empty values and unnecessarily nested lists. 345 | 346 | Args: 347 | json_obj (Any): JSON object (typically a dictionary or list, but can also be a primitive) to be cleaned. 348 | 349 | Returns: 350 | Any: Reformatted JSON list derived from original JSON object. 351 | 352 | """ 353 | if isinstance(json_obj[0], list): 354 | if len(json_obj) > 1: 355 | return reformat_json_list( 356 | [reformat_json_list(item) if isinstance(item, list) else item for item in json_obj]) 357 | else: 358 | return reformat_json_list(json_obj[0]) 359 | else: 360 | # create chain map that filters out empty lists/dicts but leaves objects where value = 0 361 | return ChainMap(*[value for value in json_obj if (value == 0 or value)]) 362 | --------------------------------------------------------------------------------