├── tests ├── __init__.py ├── models │ ├── __init__.py │ ├── test_player.py │ ├── test_team.py │ ├── test_leaguetable.py │ ├── test_competition.py │ └── test_fixture.py ├── test_football.py └── resources.py ├── pyfootball ├── models │ ├── __init__.py │ ├── player.py │ ├── fixture.py │ ├── team.py │ ├── leaguetable.py │ └── competition.py ├── __init__.py ├── globals.py └── football.py ├── docs ├── pages │ ├── faq.rst │ ├── support.rst │ ├── changelog.rst │ ├── api.rst │ ├── getting_started.rst │ └── data_model.rst ├── index.rst ├── Makefile └── conf.py ├── .travis.yml ├── LICENSE ├── setup.py ├── .gitignore ├── README.rst └── CONTRIBUTING.md /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/models/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyfootball/models/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyfootball/__init__.py: -------------------------------------------------------------------------------- 1 | from pyfootball.football import Football 2 | -------------------------------------------------------------------------------- /docs/pages/faq.rst: -------------------------------------------------------------------------------- 1 | Frequently Asked Questions 2 | =========================== 3 | Intentionally left empty for now. -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.5" 4 | # command to install dependencies 5 | install: 6 | - python setup.py install 7 | # command to run tests 8 | script: 9 | - python -m unittest discover -------------------------------------------------------------------------------- /docs/pages/support.rst: -------------------------------------------------------------------------------- 1 | Support 2 | ========= 3 | Bugs 4 | ---------------- 5 | If you believe you've found a bug with the library, feel free to create an issue on our issue tracker **with information on how to reproduce the problem**. 6 | 7 | The pyfootball issue tracker is located at https://github.com/xozzo/pyfootball/issues. 8 | 9 | Other 10 | -------- 11 | For anything else, like questions on how to use the library or why something is behaving the way it is, you can tweet me `@timorthi `_. -------------------------------------------------------------------------------- /docs/pages/changelog.rst: -------------------------------------------------------------------------------- 1 | Change Log 2 | ============ 3 | 1.0.1 (2016.11.15) 4 | -------------------- 5 | * **[FEATURE]** The ``Football`` object now uses either a kwarg or an envvar ``PYFOOTBALL_API_KEY`` to obtain an API key. 6 | * **[FIX]** Fixed models not returning expected data types. Namely, numerical types were being returned as strings. 7 | * **[DEV]** Wrote tests that cover most of the library. 8 | * **[DEV]** Added Travis CI integration. 9 | * **[OTHER]** Removed To-Do List from README file. 10 | * **[OTHER]** Added a CONTRIBUTING file including contributing guidelines. 11 | 12 | 1.0.0 (2016.10.17) 13 | -------------------- 14 | * Initial release! :) 15 | -------------------------------------------------------------------------------- /docs/pages/api.rst: -------------------------------------------------------------------------------- 1 | API 2 | ==== 3 | 4 | For every function that sends a HTTP request, an ``HTTPError`` is raised whenever the response status code is ``4XX`` or ``5XX`` which signifies that something went wrong between pyfootball sending the API a request and the API giving a response. If you believe this to be an issue with pyfootball itself, please see :doc:`support` for more information. 5 | 6 | Football 7 | ---------- 8 | This class serves as the driver/entry point for this library. 9 | 10 | .. automodule:: pyfootball.football 11 | .. autoclass:: Football 12 | :members: 13 | 14 | .. automethod:: __init__ 15 | 16 | Competition 17 | ------------- 18 | 19 | .. automodule:: pyfootball.models.competition 20 | .. autoclass:: Competition 21 | :members: 22 | 23 | Team 24 | ----- 25 | 26 | .. automodule:: pyfootball.models.team 27 | .. autoclass:: Team 28 | :members: -------------------------------------------------------------------------------- /pyfootball/models/player.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | from datetime import datetime 3 | 4 | 5 | class Player(): 6 | def __init__(self, data): 7 | """Takes a dict converted from the JSON response by the API and wraps 8 | the player data within an object. 9 | 10 | :param data: The player data from the API's response. 11 | :type data: dict 12 | """ 13 | self.name = data['name'] 14 | self.position = data['position'] 15 | self.jersey_number = data['jerseyNumber'] 16 | self.date_of_birth = datetime.strptime(data['dateOfBirth'], 17 | '%Y-%m-%d').date() 18 | self.nationality = data['nationality'] 19 | self.contract_until = datetime.strptime(data['contractUntil'], 20 | '%Y-%m-%d').date() 21 | self.market_value = data['marketValue'] 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Timothy Ng 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pyfootball/globals.py: -------------------------------------------------------------------------------- 1 | api_key = "" 2 | headers = {} 3 | prev_response = {} 4 | 5 | _base = 'http://api.football-data.org' 6 | endpoints = { 7 | 'fixture': _base + '/v1/fixtures/{}', 8 | 'all_fixtures': _base + '/v1/fixtures/', 9 | 'competition': _base + '/v1/competitions/{}', 10 | 'all_competitions': _base + '/v1/competitions/', 11 | 'comp_teams': _base + '/v1/competitions/{}/teams', 12 | 'comp_fixtures': _base + '/v1/competitions/{}/fixtures', 13 | 'team': _base + '/v1/teams/{}', 14 | 'team_players': _base + '/v1/teams/{}/players', 15 | 'team_fixtures': _base + '/v1/teams/{}/fixtures/', 16 | 'league_table': _base + '/v1/competitions/{}/leagueTable' 17 | } 18 | 19 | 20 | def update_prev_response(r, endpoint): 21 | """ Sets the prev_response attribute to contain a dict that includes 22 | the response status code and headers of the most recent HTTP 23 | request. 24 | 25 | Arguments: 26 | r -- The response object (of the latest HTTP request). 27 | endpoint -- The endpoint used (in the latest HTTP request). 28 | """ 29 | global prev_response 30 | prev_response = r.headers 31 | prev_response['Status-Code'] = r.status_code 32 | prev_response['Endpoint'] = endpoint 33 | -------------------------------------------------------------------------------- /tests/test_football.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import unittest 3 | from unittest.mock import Mock, patch 4 | 5 | from tests import resources 6 | from pyfootball.football import Football 7 | from pyfootball.models.competition import Competition 8 | 9 | 10 | class TestFootball(unittest.TestCase): 11 | @patch('pyfootball.football.requests.get') 12 | def test_constructor_invalid_kwarg(self, mock_get): 13 | with self.assertRaises(requests.exceptions.HTTPError): 14 | http_err = requests.exceptions.HTTPError() 15 | mock_response = mock_get.return_value 16 | mock_response.raise_for_status.side_effect = http_err 17 | Football(api_key="some_bogus_key") 18 | 19 | @patch('pyfootball.football.requests.get') 20 | def test_constructor_envvar(self, mock_get): 21 | try: 22 | mock_response = mock_get.return_value 23 | mock_response.raise_for_status.side_effect = None 24 | Football() # Uses PYFOOTBALL_API_KEY envvar 25 | except: 26 | self.fail() 27 | 28 | 29 | @unittest.skip("Tests yet to be written.") 30 | class TestFootballAfterInit(unittest.TestCase): 31 | def test_get_prev_response(self): 32 | pass 33 | 34 | def test_get_competition(self): 35 | pass 36 | 37 | if __name__ == '__main__': 38 | unittest.main() 39 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | import os 3 | 4 | 5 | if os.path.exists('README.rst'): 6 | readme_path = 'README.rst' 7 | else: 8 | readme_path = 'README.md' 9 | 10 | setup( 11 | name='pyfootball', 12 | version='1.0.1', 13 | description='A client library for the football-data.org REST API', 14 | long_description=open(readme_path).read(), 15 | url='https://github.com/xozzo/pyfootball', 16 | author='Timothy Ng', 17 | author_email='hello@timothyng.xyz', 18 | license='MIT', 19 | 20 | # See https://pypi.python.org/pypi?%3Aaction=list_classifiers 21 | classifiers=[ 22 | 'Development Status :: 5 - Production/Stable', 23 | 'Intended Audience :: Developers', 24 | 'Topic :: Software Development :: Libraries', 25 | 'License :: OSI Approved :: MIT License', 26 | 'Programming Language :: Python :: 3.5' 27 | ], 28 | 29 | keywords='api wrapper client library football data', 30 | 31 | packages=find_packages(exclude=['contrib', 'docs', 'tests', 'venv']), 32 | 33 | install_requires=['requests'], 34 | 35 | test_suite='tests', 36 | 37 | # List additional groups of dependencies here (e.g. development 38 | # dependencies). You can install these using the following syntax, 39 | # for example: 40 | # $ pip install -e .[dev] 41 | extras_require={ 42 | 'dev': ['sphinx', 'sphinx-autobuild'] 43 | } 44 | ) 45 | -------------------------------------------------------------------------------- /tests/models/test_player.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import traceback 3 | from datetime import date 4 | 5 | from tests import resources 6 | from pyfootball.models.player import Player 7 | 8 | 9 | class TestPlayer(unittest.TestCase): 10 | def test_init(self): 11 | try: 12 | Player(resources.PLAYER) 13 | except: 14 | self.fail() 15 | 16 | def test_init_bad_data(self): 17 | with self.assertRaises(KeyError): 18 | Player({"a": "dict", "that": "has", "bad": "data"}) 19 | 20 | 21 | class TestPlayerAfterInit(unittest.TestCase): 22 | def setUp(self): 23 | try: 24 | self.player = Player(resources.PLAYER) 25 | except Exception: 26 | print("Setting up Player object failed:") 27 | traceback.print_exc() 28 | self.skipTest(TestPlayerAfterInit) 29 | 30 | def tearDown(self): 31 | self.player = None 32 | 33 | def test_data_types(self): 34 | integers = ['jersey_number'] 35 | dates = ['date_of_birth', 'contract_until'] 36 | 37 | for attr, val in self.player.__dict__.items(): 38 | if attr in integers: 39 | self.assertIsInstance(val, int) 40 | elif attr in dates: 41 | self.assertIsInstance(val, date) 42 | else: # Strings 43 | self.assertIsInstance(val, str) 44 | 45 | if __name__ == '__main__': 46 | unittest.main() 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | 91 | config.py 92 | test.py -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | pyfootball 2 | ============ 3 | .. image:: https://readthedocs.org/projects/pyfootball/badge/?version=latest 4 | :target: http://pyfootball.readthedocs.io/en/latest/?badge=latest 5 | :alt: Documentation Status 6 | 7 | .. image:: https://travis-ci.org/xozzo/pyfootball.svg?branch=develop 8 | :target: https://travis-ci.org/xozzo/pyfootball 9 | :alt: Travis CI Status 10 | 11 | pyfootball is a client library for `football-data.org `_ written in Python. 12 | 13 | You can familiarize yourself with pyfootball's API with the `documentation `_. 14 | 15 | Requirements 16 | --------------- 17 | 18 | * A valid API key for football-data. You can request for one at ``_. 19 | * Python 3.5+ 20 | * The ``requests`` library. pip should handle this for you when installing pyfootball. 21 | 22 | Installation 23 | --------------- 24 | :: 25 | 26 | pip install pyfootball 27 | 28 | Example Usage 29 | ------------------ 30 | :: 31 | 32 | >>> import pyfootball 33 | >>> f = pyfootball.Football(api_key='your_api_key') 34 | >>> bayern = f.get_team(5) 35 | >>> bayern.market_value 36 | 582,225,000 € 37 | 38 | Support 39 | ---------- 40 | If you encounter any bugs, please let me know by `creating an issue `_ or tweeting at me `@timorthi `_. 41 | 42 | Contributing 43 | --------------- 44 | Please see the repository's `CONTRIBUTING` file. 45 | 46 | License 47 | ---------- 48 | The project is licensed under the MIT license. 49 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. _index: 2 | 3 | pyfootball 4 | ============ 5 | pyfootball is a client library for `football-data.org `_ written in Python. 6 | 7 | This library was written to allow for easier access to football-data's resources by abstracting HTTP requests and representing the JSON responses as Python classes. 8 | 9 | .. warning:: pyfootball **does not** rate limit methods that send HTTP requests to football-data's endpoints. You are responsible for adhering to the 50-requests-per-minute rule — you risk having your API key revoked and/or your IP banned if you don't! 10 | 11 | Requirements 12 | ------------- 13 | * A valid API key for football-data. You can request for one `here `_. 14 | * Python 3.5+ 15 | * The ``requests`` library. pip should handle this for you when installing pyfootball. 16 | 17 | 18 | Installation 19 | --------------- 20 | Installation is easy using pip: 21 | :: 22 | 23 | $ pip install pyfootball 24 | 25 | 26 | Example Usage 27 | -------------- 28 | >>> import pyfootball 29 | >>> f = pyfootball.Football(api_key='your_api_key') 30 | >>> bayern = f.get_team(5) 31 | >>> bayern.market_value 32 | 582,225,000 € 33 | 34 | 35 | .. toctree:: 36 | :maxdepth: 2 37 | :caption: User Documentation 38 | 39 | pages/getting_started 40 | pages/data_model 41 | pages/api 42 | 43 | 44 | .. toctree:: 45 | :maxdepth: 2 46 | :caption: About 47 | 48 | pages/faq 49 | pages/support 50 | pages/changelog 51 | 52 | 53 | License 54 | --------- 55 | The project is licensed under the MIT license. 56 | -------------------------------------------------------------------------------- /pyfootball/models/fixture.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | from datetime import datetime 3 | 4 | 5 | class Fixture(object): 6 | def __init__(self, data): 7 | """Takes a dict converted from the JSON response by the API and wraps 8 | the fixture data within an object. 9 | 10 | :param data: The fixture data from the API's response. 11 | :type data: dict 12 | """ 13 | self._home_team_ep = data['_links']['homeTeam']['href'] 14 | self._away_team_ep = data['_links']['awayTeam']['href'] 15 | self._competition_ep = data['_links']['competition']['href'] 16 | self.date = datetime.strptime(data['date'], '%Y-%m-%dT%H:%M:%SZ') 17 | self.status = data['status'] 18 | self.matchday = data['matchday'] 19 | self.home_team = data['homeTeamName'] 20 | self.home_team_id = int(self._home_team_ep.split("/")[-1]) 21 | self.away_team = data['awayTeamName'] 22 | self.away_team_id = int(self._away_team_ep.split("/")[-1]) 23 | self.competition_id = int(self._competition_ep.split("/")[-1]) 24 | 25 | if data['result']['goalsHomeTeam'] is not None: 26 | self.result = { 27 | 'home_team_goals': data['result']['goalsHomeTeam'], 28 | 'away_team_goals': data['result']['goalsAwayTeam'], 29 | } 30 | if 'halfTime' in data['result']: 31 | ht = data['result']['halfTime'] 32 | self.result['half_time'] = { 33 | 'home_team_goals': ht['goalsHomeTeam'], 34 | 'away_team_goals': ht['goalsAwayTeam'] 35 | } 36 | else: 37 | self.result = None 38 | 39 | if data['odds']: 40 | self.odds = { 41 | 'home_win': float(data['odds']['homeWin']), 42 | 'draw': float(data['odds']['draw']), 43 | 'away_win': float(data['odds']['awayWin']) 44 | } 45 | else: 46 | self.odds = None 47 | -------------------------------------------------------------------------------- /pyfootball/models/team.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | import requests 3 | 4 | from pyfootball import globals 5 | from .player import Player 6 | from .fixture import Fixture 7 | 8 | 9 | class Team(object): 10 | def __init__(self, data): 11 | """Takes a dict converted from the JSON response by the API and wraps 12 | the team data within an object. 13 | 14 | :param data: The team data from the API's response. 15 | :type data: dict 16 | """ 17 | self._fixtures_ep = data['_links']['fixtures']['href'] 18 | self._players_ep = data['_links']['players']['href'] 19 | self.id = int(data['_links']['self']['href'].split("/")[-1]) 20 | self.name = data['name'] 21 | self.code = data['code'] 22 | self.short_name = data['shortName'] 23 | self.market_value = data['squadMarketValue'] 24 | self.crest_url = data['crestUrl'] 25 | 26 | def get_fixtures(self): 27 | """Return a list of Fixture objects representing this season's 28 | fixtures for the current team. 29 | 30 | Sends one request to api.football-data.org. 31 | 32 | :returns: fixture_list: A list of Fixture objects. 33 | """ 34 | r = requests.get(self._fixtures_ep, headers=globals.headers) 35 | globals.update_prev_response(r, self._fixtures_ep) 36 | r.raise_for_status() 37 | 38 | data = r.json() 39 | fixture_list = [] 40 | for fixture in data['fixtures']: 41 | fixture_list.append(Fixture(fixture)) 42 | return fixture_list 43 | 44 | def get_players(self): 45 | """Return a list of Player objects representing players on the current 46 | team. 47 | 48 | Sends one request to api.football-data.org. 49 | 50 | :returns: player_list: A list of Player objects. 51 | """ 52 | r = requests.get(self._players_ep, headers=globals.headers) 53 | globals.update_prev_response(r, self._players_ep) 54 | r.raise_for_status() 55 | 56 | data = r.json() 57 | player_list = [] 58 | for player in data['players']: 59 | player_list.append(Player(player)) 60 | return player_list 61 | -------------------------------------------------------------------------------- /tests/models/test_team.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | import unittest 3 | from unittest.mock import Mock, patch 4 | 5 | from tests import resources 6 | from pyfootball.models.team import Team 7 | from pyfootball.models.fixture import Fixture 8 | from pyfootball.models.player import Player 9 | 10 | 11 | class TestTeam(unittest.TestCase): 12 | def test_init(self): 13 | try: 14 | Team(resources.TEAM) 15 | except: 16 | self.fail() 17 | 18 | def test_init_bad_data(self): 19 | with self.assertRaises(KeyError): 20 | Team({"a": "dict", "that": "has", "bad": "data"}) 21 | 22 | 23 | class TestTeamAfterInit(unittest.TestCase): 24 | def setUp(self): 25 | try: 26 | self.team = Team(resources.TEAM) 27 | except Exception: 28 | print("Setting up Team object failed:") 29 | traceback.print_exc() 30 | self.skipTest(TestTeamAfterInit) 31 | 32 | def tearDown(self): 33 | self.team = None 34 | 35 | def test_data_types(self): 36 | integers = ['id'] 37 | 38 | for attr, val in self.team.__dict__.items(): 39 | if attr in integers: 40 | self.assertIsInstance(val, int) 41 | else: # Strings 42 | self.assertIsInstance(val, str) 43 | 44 | @patch('pyfootball.models.team.requests.get') 45 | def test_get_fixtures(self, mock_get): 46 | mock_response = mock_get.return_value 47 | mock_response.status_code = 200 48 | mock_response.json.return_value = resources.FIXTURES 49 | 50 | fixtures = self.team.get_fixtures() 51 | self.assertIsInstance(fixtures, list) 52 | for fixture in fixtures: 53 | self.assertIsInstance(fixture, Fixture) 54 | 55 | @patch('pyfootball.models.team.requests.get') 56 | def test_get_players(self, mock_get): 57 | mock_response = mock_get.return_value 58 | mock_response.status_code = 200 59 | mock_response.json.return_value = resources.PLAYERS 60 | 61 | players = self.team.get_players() 62 | self.assertIsInstance(players, list) 63 | for player in players: 64 | self.assertIsInstance(player, Player) 65 | 66 | if __name__ == '__main__': 67 | unittest.main() 68 | -------------------------------------------------------------------------------- /tests/models/test_leaguetable.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import traceback 3 | from datetime import date 4 | 5 | from tests import resources 6 | from pyfootball.models.leaguetable import LeagueTable 7 | 8 | 9 | class TestLeagueTable(unittest.TestCase): 10 | def test_init(self): 11 | try: 12 | LeagueTable(resources.LEAGUE_TABLE) 13 | except: 14 | self.fail() 15 | 16 | def test_init_bad_data(self): 17 | with self.assertRaises(KeyError): 18 | LeagueTable({"a": "dict", "that": "has", "bad": "data"}) 19 | 20 | 21 | class TestLeagueTableAfterInit(unittest.TestCase): 22 | def setUp(self): 23 | try: 24 | self.table = LeagueTable(resources.LEAGUE_TABLE) 25 | except Exception: 26 | print("Setting up LeagueTable object failed:") 27 | traceback.print_exc() 28 | self.skipTest(TestPlayerAfterInit) 29 | 30 | def tearDown(self): 31 | self.table = None 32 | 33 | def test_data_types(self): 34 | strings = ['_competition_ep', 'competition_name', 'team_name', 35 | 'crest_url'] 36 | lists = ['standings'] 37 | 38 | for attr, val in self.table.__dict__.items(): 39 | if attr in strings: 40 | self.assertIsInstance(val, str) 41 | elif attr in lists: 42 | self.assertIsInstance(val, list) 43 | else: # Integers 44 | self.assertIsInstance(val, int) 45 | 46 | dicts = ['home', 'away'] 47 | for attr, val in self.table.standings[1].__dict__.items(): 48 | """ Test only one Standing object. It can be assumed that every 49 | other Standing object in table.standings passes this test. 50 | """ 51 | if attr in strings: 52 | self.assertIsInstance(val, str) 53 | elif attr in dicts: 54 | """The home and away dicts only contain keys which have integer 55 | values. 56 | """ 57 | for key, value in val.items(): 58 | self.assertIsInstance(value, int) 59 | 60 | else: # Integers 61 | self.assertIsInstance(val, int) 62 | 63 | if __name__ == '__main__': 64 | unittest.main() 65 | -------------------------------------------------------------------------------- /pyfootball/models/leaguetable.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | 3 | 4 | class LeagueTable(object): 5 | def __init__(self, data): 6 | """Takes a dict converted from the JSON response by the API and wraps 7 | the league table data within an object. 8 | 9 | :param data: The league table data from the API's response. 10 | :type data: dict 11 | """ 12 | self._competition_ep = data['_links']['competition']['href'] 13 | self.competition_id = int(self._competition_ep.split('/')[-1]) 14 | self.competition_name = data['leagueCaption'] 15 | self.current_matchday = data['matchday'] 16 | 17 | _standings_list = [None] 18 | for pos in data['standing']: 19 | _standings_list.append(self.Standing(pos)) 20 | 21 | self.standings = _standings_list 22 | 23 | class Standing(object): 24 | def __init__(self, data): 25 | """A private LeagueTable class that stores information about 26 | a given position in the table. 27 | """ 28 | self.position = data['position'] 29 | self.team_id = int(data['_links']['team']['href'].split('/')[-1]) 30 | self.team_name = data['teamName'] 31 | self.crest_url = data['crestURI'] 32 | self.games_played = data['playedGames'] 33 | self.points = data['points'] 34 | self.goals = data['goals'] 35 | self.goals_against = data['goalsAgainst'] 36 | self.goal_difference = data['goalDifference'] 37 | self.wins = data['wins'] 38 | self.draws = data['draws'] 39 | self.losses = data['losses'] 40 | self.home = { 41 | 'goals': data['home']['goals'], 42 | 'goals_against': data['home']['goalsAgainst'], 43 | 'wins': data['home']['wins'], 44 | 'draws': data['home']['draws'], 45 | 'losses': data['home']['losses'] 46 | } 47 | self.away = { 48 | 'goals': data['away']['goals'], 49 | 'goals_against': data['away']['goalsAgainst'], 50 | 'wins': data['away']['wins'], 51 | 'draws': data['away']['draws'], 52 | 'losses': data['away']['losses'] 53 | } 54 | -------------------------------------------------------------------------------- /tests/models/test_competition.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | import unittest 3 | from unittest.mock import Mock, patch 4 | from datetime import datetime 5 | 6 | from tests import resources 7 | from pyfootball.models.competition import Competition 8 | from pyfootball.models.team import Team 9 | from pyfootball.models.fixture import Fixture 10 | from pyfootball.models.leaguetable import LeagueTable 11 | 12 | 13 | class TestCompetition(unittest.TestCase): 14 | def test_init(self): 15 | try: 16 | Competition(resources.COMPETITION) 17 | except: 18 | self.fail() 19 | 20 | def test_init_bad_data(self): 21 | with self.assertRaises(KeyError): 22 | Competition({"a": "dict", "that": "has", "bad": "data"}) 23 | 24 | 25 | class TestCompetitionAfterInit(unittest.TestCase): 26 | def setUp(self): 27 | try: 28 | self.comp = Competition(resources.COMPETITION) 29 | except Exception: 30 | print("Setting up Competition object failed:") 31 | traceback.print_exc() 32 | self.skipTest(TestCompetitionAfterInit) 33 | 34 | def tearDown(self): 35 | self.comp = None 36 | 37 | def test_data_types(self): 38 | strings = ['_teams_ep', '_fixtures_ep', '_league_table_ep', 39 | 'name', 'code'] 40 | datetimes = ['last_updated'] 41 | 42 | for attr, val in self.comp.__dict__.items(): 43 | if attr in strings: 44 | self.assertIsInstance(val, str) 45 | elif attr in datetimes: 46 | self.assertIsInstance(val, datetime) 47 | else: # Integers 48 | self.assertIsInstance(val, int) 49 | 50 | @patch('pyfootball.models.competition.requests.get') 51 | def test_get_fixtures(self, mock_get): 52 | mock_response = mock_get.return_value 53 | mock_response.status_code = 200 54 | mock_response.json.return_value = resources.FIXTURES 55 | 56 | fixtures = self.comp.get_fixtures() 57 | self.assertIsInstance(fixtures, list) 58 | for fixture in fixtures: 59 | self.assertIsInstance(fixture, Fixture) 60 | 61 | @patch('pyfootball.models.competition.requests.get') 62 | def test_get_teams(self, mock_get): 63 | mock_response = mock_get.return_value 64 | mock_response.status_code = 200 65 | mock_response.json.return_value = resources.COMP_TEAMS 66 | 67 | teams = self.comp.get_teams() 68 | self.assertIsInstance(teams, list) 69 | for team in teams: 70 | self.assertIsInstance(team, Team) 71 | 72 | @patch('pyfootball.models.competition.requests.get') 73 | def test_get_league_table(self, mock_get): 74 | mock_response = mock_get.return_value 75 | mock_response.status_code = 200 76 | mock_response.json.return_value = resources.LEAGUE_TABLE 77 | 78 | table = self.comp.get_league_table() 79 | self.assertIsInstance(table, LeagueTable) 80 | -------------------------------------------------------------------------------- /pyfootball/models/competition.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | import requests 3 | from datetime import datetime 4 | 5 | from pyfootball import globals 6 | from .team import Team 7 | from .fixture import Fixture 8 | from .leaguetable import LeagueTable 9 | 10 | 11 | class Competition(): 12 | def __init__(self, data): 13 | """Takes a dict converted from the JSON response by the API and wraps 14 | the competition data within an object. 15 | 16 | :param data: The competition data from the API's response. 17 | :type data: dict 18 | """ 19 | self._teams_ep = data['_links']['teams']['href'] 20 | self._fixtures_ep = data['_links']['fixtures']['href'] 21 | self._league_table_ep = data['_links']['leagueTable']['href'] 22 | self.id = data['id'] 23 | self.name = data['caption'] 24 | self.code = data['league'] 25 | self.year = int(data['year']) 26 | self.current_matchday = data['currentMatchday'] 27 | self.number_of_matchdays = data['numberOfMatchdays'] 28 | self.number_of_teams = data['numberOfTeams'] 29 | self.number_of_games = data['numberOfGames'] 30 | self.last_updated = datetime.strptime(data['lastUpdated'], 31 | '%Y-%m-%dT%H:%M:%SZ') 32 | 33 | def get_fixtures(self): 34 | """Return a list of Fixture objects representing the fixtures in this 35 | competition for the current season. 36 | 37 | Sends one request to api.football-data.org. 38 | 39 | :returns: fixture_list: A list of Fixture objects. 40 | """ 41 | r = requests.get(self._fixtures_ep, headers=globals.headers) 42 | globals.update_prev_response(r, self._fixtures_ep) 43 | r.raise_for_status() 44 | 45 | data = r.json() 46 | fixture_list = [] 47 | for fixture in data['fixtures']: 48 | fixture_list.append(Fixture(fixture)) 49 | return fixture_list 50 | 51 | def get_teams(self): 52 | """Return a list of Team objects representing the teams in this 53 | competition for the current season. 54 | 55 | Sends one request to api.football-data.org. 56 | 57 | :returns: team_list: A list of Team objects. 58 | """ 59 | r = requests.get(self._teams_ep, headers=globals.headers) 60 | globals.update_prev_response(r, self._teams_ep) 61 | r.raise_for_status() 62 | 63 | data = r.json() 64 | team_list = [] 65 | for tm in data['teams']: 66 | team_list.append(Team(tm)) 67 | return team_list 68 | 69 | def get_league_table(self): 70 | """Return the league table for this competition. 71 | 72 | Sends one request to api.football-data.org. 73 | 74 | :returns: LeagueTable: A LeagueTable object. 75 | """ 76 | r = requests.get(self._league_table_ep, headers=globals.headers) 77 | globals.update_prev_response(r, self._league_table_ep) 78 | r.raise_for_status() 79 | 80 | return LeagueTable(r.json()) 81 | -------------------------------------------------------------------------------- /tests/models/test_fixture.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import traceback 3 | from datetime import datetime 4 | 5 | from tests import resources 6 | from pyfootball.models.fixture import Fixture 7 | 8 | 9 | class TestFixture(unittest.TestCase): 10 | def test_init(self): 11 | try: 12 | Fixture(resources.FIXTURE) 13 | Fixture(resources.FIXTURE_WITH_HT_AND_ODDS) 14 | except: 15 | self.fail() 16 | 17 | def test_init_bad_data(self): 18 | with self.assertRaises(KeyError): 19 | Fixture({"a": "dict", "that": "has", "bad": "data"}) 20 | 21 | 22 | class TestFixtureAfterInit(unittest.TestCase): 23 | def setUp(self): 24 | try: 25 | self.fixture = Fixture(resources.FIXTURE) 26 | self.fixture_full = Fixture(resources.FIXTURE_WITH_HT_AND_ODDS) 27 | except Exception: 28 | print("Setting up Fixture object(s) failed:") 29 | traceback.print_exc() 30 | self.skipTest(TestFixtureAfterInit) 31 | 32 | def tearDown(self): 33 | self.fixture = None 34 | self.fixture_full = None 35 | 36 | def test_data_types(self): 37 | integers = ['matchday', 'home_team_id', 'away_team_id', 38 | 'competition_id', 'home_team_goals, away_team_goals'] 39 | dicts = ['result', 'odds'] 40 | datetimes = ['date'] 41 | 42 | for attr, val in self.fixture.__dict__.items(): 43 | if attr in integers: 44 | self.assertIsInstance(val, int) 45 | elif attr in datetimes: 46 | self.assertIsInstance(val, datetime) 47 | elif attr in dicts: 48 | if attr is 'result': 49 | for key, value in val.items(): # results dict 50 | if key is 'half_time': 51 | self.assertEqual(value, None) 52 | else: 53 | self.assertIsInstance(value, int) 54 | elif attr is 'odds': 55 | self.assertEqual(val, None) 56 | else: # Strings 57 | self.assertIsInstance(val, str) 58 | 59 | for attr, val in self.fixture_full.__dict__.items(): 60 | if attr in integers: 61 | self.assertIsInstance(val, int) 62 | elif attr in datetimes: 63 | self.assertIsInstance(val, datetime) 64 | elif attr in dicts: 65 | if attr is 'result': 66 | for key, value in val.items(): # results dict 67 | if key is 'half_time': 68 | for k, v in value.items(): # half_time dict 69 | self.assertIsInstance(v, int) 70 | else: 71 | self.assertIsInstance(value, int) 72 | elif attr is 'odds': 73 | for key, value in val.items(): # odds dict 74 | self.assertIsInstance(value, float) 75 | else: # Strings 76 | self.assertIsInstance(val, str) 77 | 78 | if __name__ == '__main__': 79 | unittest.main() 80 | -------------------------------------------------------------------------------- /docs/pages/getting_started.rst: -------------------------------------------------------------------------------- 1 | Getting Started 2 | ================= 3 | In this tutorial, you'll be introduced to pyfootball's API as well as its data mapping. 4 | 5 | If you're not familiar with football-data.org, it'd be better for you to get acquainted with it by reading the `football-data.org documentation `_ before proceeding with pyfootball. 6 | 7 | If you don't have pyfootball set up, see :doc:`the home page <../index>`. Otherwise, let's get started! 8 | 9 | First, you're going to want to create a ``Football`` instance: 10 | :: 11 | 12 | >>> import pyfootball 13 | >>> f = pyfootball.Football(api_key='your_api_key') 14 | 15 | You can also choose to instantiate ``Football`` without any arguments and make 16 | it use an API key obtained from an environmental variable named 17 | ``PYFOOTBALL_API_KEY``. Here is an example in \*nix: 18 | :: 19 | 20 | $ export PYFOOTBALL_API_KEY='your_api_key' 21 | 22 | and then in your program: 23 | :: 24 | 25 | >>> import pyfootball 26 | >>> f = pyfootball.Football() 27 | 28 | If you provide an invalid API key, an HTTPError exception will be raised. 29 | 30 | .. note:: Instantiating a ``Football`` object will use one request out of the 50 allowed per minute by football-data.org's API. You can see the full list of which functions send requests and which ones don't at :doc:`api`. 31 | 32 | The ``Football`` class serves as an entry point for the library. Now, we want to get the data of a team — for example, Manchester United — but since we don't know its ID in football-data.org's database, we're going to have to look it up: 33 | :: 34 | 35 | >>> matches = f.search_teams("manchester") 36 | >>> matches 37 | {65: 'Manchester City FC', 66: 'Manchester United FC'} 38 | 39 | ``Football.search_teams(name)`` queries the database for matches to ``name`` and returns key-value pairs of team IDs and team names respectively. 40 | 41 | Now that we have Manchester United's ID, we can get more information about it: 42 | :: 43 | 44 | >>> man_utd = f.get_team(66) 45 | 46 | ``Football.get_team(id)`` returns a ``Team`` object. It contains all the information you'd get in a JSON response from football-data.org, along with some cool functions. We can call ``Team.get_fixtures()`` to get its fixtures or ``Team.get_players()`` to get its players. 47 | 48 | .. hint:: The ``Football`` class provides a useful method ``Football.get_prev_response()`` to give you information about the most recently-used response. Any time you use a method in the library that sends a HTTP request, this value is updated. You can use it to keep track of useful stuff like response status code or how many requests you have left. 49 | 50 | :: 51 | 52 | >>> players = man_utd.get_players() 53 | 54 | ``Team.get_players()`` returns a list of ``Player`` objects. Like ``Team`` objects, ``Player`` objects are objects from JSON responses mapped to Python classes: 55 | :: 56 | 57 | >>> players[0].name 58 | Paul Pogba 59 | >>> players[0].market_value 60 | 70,000,000 € 61 | 62 | A comprehensive list of object models and their attributes are available at :doc:`data_model`. A full list of functions available are available at :doc:`api`. 63 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | #Contributing to pyfootball 2 | 3 | ## Table of Contents 4 | [**Before You Contribute**](#before-you-contribute) 5 | - [About pyfootball](#about-pyfootball) 6 | - [Setting Up](#setting-up) 7 | - [Issue Tracker](#issue-tracker) 8 | 9 | [**Making Changes**](#making-changes) 10 | - [Coding Style](#coding-style) 11 | - [Tests](#tests) 12 | - [Documentation](#documentation) 13 | 14 | [**Pull Requests**](#pull-requests) 15 | 16 | 17 | ## Before You Contribute 18 | 19 | ### About pyfootball 20 | 21 | pyfootball is a wrapper around the [football-data.org](http://football-data.org/) API. It is highly recommended that you familiarize yourself with its structure before using or working on pyfootball. To use or contribute to pyfootball, you're first going to need an API key for football-data; you can request for one [here](http://api.football-data.org/register). 22 | 23 | ### Setting Up 24 | 25 | * Fork and clone the repository. 26 | * (Optional) Set up an isolated Python 3 environment with [virtualenv](http://docs.python-guide.org/en/latest/dev/virtualenvs/). Do this if you don't want to pollute your global `site-packages` directory. 27 | * Install the package and its dependencies: `pip install -e .[dev]` 28 | 29 | To verify that your installation works as expected, set up an envvar with your API key: 30 | ` $ export PYFOOTBALL_API_KEY='your_api_key'` 31 | and then run the tests from the project's root directory: 32 | ` $ python -m unittest` 33 | There is only one unit test that sends an actual request to football-data. Don't worry about exceeding your request limit. 34 | 35 | ### Issue Tracker 36 | 37 | The pyfootball issue tracker is located in the repository itself at [https://github.com/xozzo/pyfootball/issues](https://github.com/xozzo/pyfootball/issues). If you see something you'd like to work on, leave a comment on it so that other contributors will know. If there's something like you'd like to work on but it's not on the issue list, feel free to create an issue! 38 | 39 | ## Making Changes 40 | ### Coding Style 41 | This project follows closely with the [PEP8](https://www.python.org/dev/peps/pep-0008/) style guide, including but not limited to these rules: 42 | 43 | * Set indentations to use 4 spaces 44 | * Do not exceed 80 characters per line 45 | * Use underscores as word separators in variable and method names: `my_var` instead of `myVar` 46 | * Use CamelCase (first letter capitalized) for class names 47 | 48 | ### Tests 49 | If you made any minor changes to the code base, be sure that the tests still pass. If you made any major changes (API-breaking changes or new functions/classes) to the code base, please overwrite an existing test or write a new test respectively. 50 | 51 | ### Documentation 52 | If any changes were made to the code base, at the very least they should be reflected, in a descriptive manner, on the changelog page in the documentation. Classify the changes with either **[FEATURE]** for new features, **[FIX]** for bug fixes, **[DEV]** for changes to the dev environment, or **[OTHER]** for anything else. Do not worry about versioning; this will be decided by main contributors when you make a pull request. 53 | 54 | pyfootball uses the Sphinx tool to generate API documentation. Sphinx does this by parsing docstrings written in reStructuredText format. Have a look at the Sphinx [rST primer](http://www.sphinx-doc.org/en/stable/rest.html) and [autodoc page](http://www.sphinx-doc.org/en/1.4.8/ext/autodoc.html) for more information. 55 | 56 | 57 | ## Pull Requests 58 | Once you're done with testing your changes, submit a pull request to the **dev branch** of the [main repository](https://github.com/xozzo/pyfootball). If everything looks good, your pull request will be merged. If you were working on something listed in the issue tracker, be sure to reference the pull request in the respective issue. 59 | -------------------------------------------------------------------------------- /docs/pages/data_model.rst: -------------------------------------------------------------------------------- 1 | Data Model 2 | ============= 3 | The data model was designed to keep to the original data's structure as closely as possible. There were mostly minor changes as a result of following the `PEP8 guidelines `_ such as turning variable names from using ``camelCase`` to ``under_scores``. 4 | 5 | Each `football-data.org resource `_ is mapped into an object. Each value in a JSON resource is mapped to an attribute of the object. You can access these values using the syntax ``Object.attribute``. For example: 6 | :: 7 | 8 | >>> import pyfootball 9 | >>> f = pyfootball.Football(api_key='your_api_key') 10 | >>> my_team = f.get_team(5) 11 | >>> my_team.name 12 | FC Bayern München 13 | 14 | Competition 15 | ------------ 16 | 17 | .. list-table:: 18 | :widths: 40 40 100 19 | :header-rows: 1 20 | 21 | * - Attribute 22 | - Type 23 | - Description 24 | 25 | * - id 26 | - integer 27 | - The ID of the competition. 28 | 29 | * - name 30 | - string 31 | - The name of the competition. 32 | 33 | * - code 34 | - string 35 | - The `League Code `_ of the competition. 36 | 37 | * - year 38 | - integer 39 | - The year in which the competition started. For example, the ``year`` for a 16/17 competition would be 2016. 40 | 41 | * - current_matchday 42 | - integer 43 | - The competition's current matchday. 44 | 45 | * - number_of_matchdays 46 | - integer 47 | - The number of matchdays in this competition. 48 | 49 | * - number_of_teams 50 | - integer 51 | - The number of teams competing in this competition. 52 | 53 | * - number_of_games 54 | - integer 55 | - The number of games in this competition. 56 | 57 | * - last_updated 58 | - datetime 59 | - The date and time at which this resource was last updated. 60 | 61 | LeagueTable 62 | ------------- 63 | 64 | .. list-table:: 65 | :widths: 40 40 100 66 | :header-rows: 1 67 | 68 | * - Attribute 69 | - Type 70 | - Description 71 | 72 | * - competition_id 73 | - integer 74 | - The competition ID for this league table. 75 | 76 | * - competition_name 77 | - string 78 | - The competition name for this league table. 79 | 80 | * - current_matchday 81 | - id 82 | - The current matchday. 83 | 84 | * - standings 85 | - list 86 | - A list of ``Standing`` objects. The list is one-indexed so as to correspond with the position in the table (i.e. standings[1] is the top of the table) 87 | 88 | Standing 89 | ^^^^^^^^^^ 90 | Each ``Standing`` object represents a "row" in the league table. 91 | 92 | .. list-table:: 93 | :widths: 40 40 100 94 | :header-rows: 1 95 | 96 | * - Attribute 97 | - Type 98 | - Description 99 | 100 | * - team_id 101 | - integer 102 | - The team ID. 103 | 104 | * - team_name 105 | - string 106 | - The team name. 107 | 108 | * - crest_url 109 | - string 110 | - A link to an image of the team's crest. 111 | 112 | * - position 113 | - integer 114 | - The current team's position. 115 | 116 | * - games_played 117 | - integer 118 | - The number of games played by this team. 119 | 120 | * - points 121 | - integer 122 | - The number of points that this team has. 123 | 124 | * - goals 125 | - integer 126 | - The number of goals scored by this team. 127 | 128 | * - goals_against 129 | - integer 130 | - The number of goals conceded by this team. 131 | 132 | * - goal_difference 133 | - integer 134 | - ``(goals - goals_against)`` 135 | 136 | * - wins 137 | - integer 138 | - The number of wins this team has. 139 | 140 | * - draws 141 | - integer 142 | - The number of draws this team has. 143 | 144 | * - losses 145 | - integer 146 | - The number of losses this team has. 147 | 148 | * - home 149 | - dict 150 | - Contains ``goals``, ``goals_against``, ``wins``, ``draws``, and ``losses`` keys with integer values that represent home stats. 151 | 152 | * - away 153 | - dict 154 | - Contains ``goals``, ``goals_against``, ``wins``, ``draws``, and ``losses`` keys with integer values that represent away stats. 155 | 156 | 157 | 158 | Fixture 159 | --------- 160 | 161 | .. list-table:: 162 | :widths: 40 40 100 163 | :header-rows: 1 164 | 165 | * - Attribute 166 | - Type 167 | - Description 168 | 169 | * - date 170 | - datetime 171 | - The fixture date and time. 172 | 173 | * - status 174 | - string 175 | - The status of this fixture. 176 | 177 | * - matchday 178 | - integer 179 | - The matchday on which this fixture is set. 180 | 181 | * - home_team 182 | - string 183 | - The name of the home team. 184 | 185 | * - home_team_id 186 | - integer 187 | - The ID of the home team. 188 | 189 | * - away_team 190 | - string 191 | - The name of the away team. 192 | 193 | * - away_team_id 194 | - integer 195 | - The ID of the away team. 196 | 197 | * - competition_id 198 | - integer 199 | - The ID of the competition for this fixture. 200 | 201 | * - result 202 | - dict 203 | - The result for this fixture. ``None`` if the match is not complete. Otherwise, contains ``home_team_goals`` and ``away_team_goals`` keys with integer values. Some ``Fixtures`` have a ``half_time`` key set for the score at half time. 204 | 205 | * - odds 206 | - dict 207 | - The betting odds for this fixture. ``None`` if not available. Otherwise, contains ``home_win``, ``draw`` and ``away_win`` keys with float values. 208 | 209 | Team 210 | ------ 211 | 212 | .. list-table:: 213 | :widths: 40 40 100 214 | :header-rows: 1 215 | 216 | * - Attribute 217 | - Type 218 | - Description 219 | 220 | * - id 221 | - integer 222 | - The team ID. 223 | 224 | * - name 225 | - string 226 | - The team name. 227 | 228 | * - code 229 | - string 230 | - The team code (e.g. Borussia Dortmund's code is BVB). 231 | 232 | * - short_name 233 | - string 234 | - The team's short name. 235 | 236 | * - market_value 237 | - string 238 | - The collective market value of the team's squad. 239 | 240 | * - crest_url 241 | - string 242 | - A link to an image of the team's crest. 243 | 244 | Player 245 | ------ 246 | 247 | .. list-table:: 248 | :widths: 40 40 100 249 | :header-rows: 1 250 | 251 | * - Attribute 252 | - Type 253 | - Description 254 | 255 | * - name 256 | - string 257 | - The player's name. 258 | 259 | * - position 260 | - string 261 | - The player's position on the field. 262 | 263 | * - jersey_number 264 | - integer 265 | - The player's kit number. 266 | 267 | * - date_of_birth 268 | - date 269 | - The player's date of birth. 270 | 271 | * - nationality 272 | - string 273 | - The player's nationality. 274 | 275 | * - contract_until 276 | - date 277 | - The date of the player's contract expiry with their team. 278 | 279 | * - market_value 280 | - string 281 | - The player's market value. -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help 18 | help: 19 | @echo "Please use \`make ' where is one of" 20 | @echo " html to make standalone HTML files" 21 | @echo " dirhtml to make HTML files named index.html in directories" 22 | @echo " singlehtml to make a single large HTML file" 23 | @echo " pickle to make pickle files" 24 | @echo " json to make JSON files" 25 | @echo " htmlhelp to make HTML files and a HTML help project" 26 | @echo " qthelp to make HTML files and a qthelp project" 27 | @echo " applehelp to make an Apple Help Book" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " epub3 to make an epub3" 31 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 32 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 33 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 34 | @echo " text to make text files" 35 | @echo " man to make manual pages" 36 | @echo " texinfo to make Texinfo files" 37 | @echo " info to make Texinfo files and run them through makeinfo" 38 | @echo " gettext to make PO message catalogs" 39 | @echo " changes to make an overview of all changed/added/deprecated items" 40 | @echo " xml to make Docutils-native XML files" 41 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 42 | @echo " linkcheck to check all external links for integrity" 43 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 44 | @echo " coverage to run coverage check of the documentation (if enabled)" 45 | @echo " dummy to check syntax errors of document sources" 46 | 47 | .PHONY: clean 48 | clean: 49 | rm -rf $(BUILDDIR)/* 50 | 51 | .PHONY: html 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | .PHONY: dirhtml 58 | dirhtml: 59 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 60 | @echo 61 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 62 | 63 | .PHONY: singlehtml 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | .PHONY: pickle 70 | pickle: 71 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 72 | @echo 73 | @echo "Build finished; now you can process the pickle files." 74 | 75 | .PHONY: json 76 | json: 77 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 78 | @echo 79 | @echo "Build finished; now you can process the JSON files." 80 | 81 | .PHONY: htmlhelp 82 | htmlhelp: 83 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 84 | @echo 85 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 86 | ".hhp project file in $(BUILDDIR)/htmlhelp." 87 | 88 | .PHONY: qthelp 89 | qthelp: 90 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 91 | @echo 92 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 93 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 94 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pyfootball.qhcp" 95 | @echo "To view the help file:" 96 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pyfootball.qhc" 97 | 98 | .PHONY: applehelp 99 | applehelp: 100 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 101 | @echo 102 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 103 | @echo "N.B. You won't be able to view it unless you put it in" \ 104 | "~/Library/Documentation/Help or install it in your application" \ 105 | "bundle." 106 | 107 | .PHONY: devhelp 108 | devhelp: 109 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 110 | @echo 111 | @echo "Build finished." 112 | @echo "To view the help file:" 113 | @echo "# mkdir -p $$HOME/.local/share/devhelp/pyfootball" 114 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pyfootball" 115 | @echo "# devhelp" 116 | 117 | .PHONY: epub 118 | epub: 119 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 120 | @echo 121 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 122 | 123 | .PHONY: epub3 124 | epub3: 125 | $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 126 | @echo 127 | @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." 128 | 129 | .PHONY: latex 130 | latex: 131 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 132 | @echo 133 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 134 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 135 | "(use \`make latexpdf' here to do that automatically)." 136 | 137 | .PHONY: latexpdf 138 | latexpdf: 139 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 140 | @echo "Running LaTeX files through pdflatex..." 141 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 142 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 143 | 144 | .PHONY: latexpdfja 145 | latexpdfja: 146 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 147 | @echo "Running LaTeX files through platex and dvipdfmx..." 148 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 149 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 150 | 151 | .PHONY: text 152 | text: 153 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 154 | @echo 155 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 156 | 157 | .PHONY: man 158 | man: 159 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 160 | @echo 161 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 162 | 163 | .PHONY: texinfo 164 | texinfo: 165 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 166 | @echo 167 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 168 | @echo "Run \`make' in that directory to run these through makeinfo" \ 169 | "(use \`make info' here to do that automatically)." 170 | 171 | .PHONY: info 172 | info: 173 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 174 | @echo "Running Texinfo files through makeinfo..." 175 | make -C $(BUILDDIR)/texinfo info 176 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 177 | 178 | .PHONY: gettext 179 | gettext: 180 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 181 | @echo 182 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 183 | 184 | .PHONY: changes 185 | changes: 186 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 187 | @echo 188 | @echo "The overview file is in $(BUILDDIR)/changes." 189 | 190 | .PHONY: linkcheck 191 | linkcheck: 192 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 193 | @echo 194 | @echo "Link check complete; look for any errors in the above output " \ 195 | "or in $(BUILDDIR)/linkcheck/output.txt." 196 | 197 | .PHONY: doctest 198 | doctest: 199 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 200 | @echo "Testing of doctests in the sources finished, look at the " \ 201 | "results in $(BUILDDIR)/doctest/output.txt." 202 | 203 | .PHONY: coverage 204 | coverage: 205 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 206 | @echo "Testing of coverage in the sources finished, look at the " \ 207 | "results in $(BUILDDIR)/coverage/python.txt." 208 | 209 | .PHONY: xml 210 | xml: 211 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 212 | @echo 213 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 214 | 215 | .PHONY: pseudoxml 216 | pseudoxml: 217 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 218 | @echo 219 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 220 | 221 | .PHONY: dummy 222 | dummy: 223 | $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy 224 | @echo 225 | @echo "Build finished. Dummy builder generates no files." 226 | -------------------------------------------------------------------------------- /pyfootball/football.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import os 3 | 4 | from . import globals 5 | from .globals import endpoints 6 | from .models.competition import Competition 7 | from .models.team import Team 8 | from .models.fixture import Fixture 9 | from .models.leaguetable import LeagueTable 10 | from .models.player import Player 11 | 12 | 13 | class Football(object): 14 | def __init__(self, api_key=None): 15 | """Takes either an api_key as a keyword argument or tries to access 16 | an environmental variable ``PYFOOTBALL_API_KEY``, then uses the key to 17 | send a test request to make sure that it's valid. The api_key 18 | kwarg takes precedence over the envvar. 19 | 20 | Sends one request to api.football-data.org. 21 | 22 | :keyword api_key: The user's football-data.org API key. 23 | :type api_key: string 24 | """ 25 | if api_key: 26 | key = api_key 27 | elif os.getenv('PYFOOTBALL_API_KEY', None): 28 | key = os.getenv('PYFOOTBALL_API_KEY') 29 | else: 30 | raise ValueError("Couldn't find an API key in the keyword " + 31 | "argument api_key nor the environmental " + 32 | "variable PYFOOTBALL_API_KEY.") 33 | 34 | endpoint = endpoints['all_competitions'] 35 | globals.headers = {'X-Auth-Token': key} 36 | r = requests.get(endpoint, headers=globals.headers) 37 | globals.update_prev_response(r, endpoint) 38 | r.raise_for_status() 39 | globals.api_key = key 40 | 41 | def get_prev_response(self): 42 | """Returns information about the most recent response. 43 | 44 | :returns: prev_response: Information about the most recent response. 45 | """ 46 | return globals.prev_response 47 | 48 | def get_competition(self, comp_id): 49 | """Returns a Competition object associated with the competition ID. 50 | 51 | Sends one request to api.football-data.org. 52 | 53 | :param comp_id: The competition ID. 54 | :type comp_id: integer 55 | 56 | :returns: Competition: The Competition object. 57 | """ 58 | endpoint = endpoints['competition'].format(comp_id) 59 | r = requests.get(endpoint, headers=globals.headers) 60 | globals.update_prev_response(r, endpoint) 61 | r.raise_for_status() 62 | 63 | return Competition(r.json()) 64 | 65 | def get_all_competitions(self): 66 | """Returns a list of Competition objects representing the current 67 | season's competitions. 68 | 69 | Sends one request to api.football-data.org. 70 | 71 | :returns: comp_list: List of Competition objects. 72 | """ 73 | endpoint = endpoints['all_competitions'] 74 | r = requests.get(endpoint, headers=globals.headers) 75 | globals.update_prev_response(r, endpoint) 76 | r.raise_for_status() 77 | 78 | data = r.json() 79 | comp_list = [] 80 | for comp in data: 81 | comp_list.append(Competition(comp)) 82 | return comp_list 83 | 84 | def get_league_table(self, comp_id): 85 | """Given a competition ID, returns a LeagueTable object for the 86 | league table associated with the competition. 87 | 88 | Sends one request to api.football-data.org. 89 | 90 | :param comp_id: The competition ID. 91 | :type comp_id: integer 92 | 93 | :returns: LeagueTable: A LeagueTable object. 94 | """ 95 | endpoint = endpoints['league_table'].format(comp_id) 96 | r = requests.get(endpoint, headers=globals.headers) 97 | globals.update_prev_response(r, endpoint) 98 | r.raise_for_status() 99 | 100 | return LeagueTable(r.json()) 101 | 102 | def get_comp_fixtures(self, comp_id): 103 | """Given an ID, returns a list of Fixture objects associated with the 104 | given competition. 105 | 106 | Sends one request to api.football-data.org. 107 | 108 | :param comp_id: The competition ID. 109 | :type comp_id: integer 110 | 111 | :returns: fixture_list: A list of Fixture objects. 112 | """ 113 | endpoint = endpoints['comp_fixtures'].format(comp_id) 114 | r = requests.get(endpoint, headers=globals.headers) 115 | globals.update_prev_response(r, endpoint) 116 | r.raise_for_status() 117 | 118 | data = r.json() 119 | fixture_list = [] 120 | for fixture in data['fixtures']: 121 | fixture_list.append(Fixture(fixture)) 122 | return fixture_list 123 | 124 | def get_competition_teams(self, comp_id): 125 | """Given an ID, returns a list of Team objects associated with the 126 | given competition. 127 | 128 | Sends one request to api.football-data.org. 129 | 130 | :param comp_id: The competition ID. 131 | :type comp_id: integer 132 | 133 | :returns: team_list: A list of Team objects. 134 | """ 135 | endpoint = endpoints['comp_teams'].format(comp_id) 136 | r = requests.get(endpoint, headers=globals.headers) 137 | globals.update_prev_response(r, endpoint) 138 | r.raise_for_status() 139 | 140 | data = r.json() 141 | team_list = [] 142 | for tm in data['teams']: 143 | team_list.append(Team(tm)) 144 | return team_list 145 | 146 | def get_fixture(self, fixture_id): 147 | """Returns a Fixture object associated with the given ID. The response 148 | includes a head-to-head between teams; this will be implemented 149 | in the near future. 150 | 151 | Sends one request to api.football-data.org. 152 | 153 | :param fixture_id: The fixture ID. 154 | :type fixture_id: integer 155 | 156 | :returns: Fixture: A Fixture object. 157 | """ 158 | endpoint = endpoints['fixture'].format(fixture_id) 159 | r = requests.get(endpoint, headers=globals.headers) 160 | globals.update_prev_response(r, endpoint) 161 | r.raise_for_status() 162 | 163 | return Fixture(r.json()['fixture']) 164 | 165 | def get_all_fixtures(self): 166 | """Returns a list of all Fixture objects in the specified time frame. 167 | Defaults to the next 7 days or "n7". TODO: Include timeFrameStart 168 | and timeFrameEnd, and filter for specifying time frame. 169 | 170 | Sends one request to api.football-data.org. 171 | 172 | :returns: fixture_list: A list of Fixture objects. 173 | """ 174 | endpoint = endpoints['all_fixtures'] 175 | r = requests.get(endpoint, headers=globals.headers) 176 | globals.update_prev_response(r, endpoint) 177 | r.raise_for_status() 178 | 179 | data = r.json() 180 | fixture_list = [] 181 | for fixture in data['fixtures']: 182 | fixture_list.append(Fixture(fixture)) 183 | return fixture_list 184 | 185 | def get_team(self, team_id): 186 | """Given an ID, returns a Team object for the team associated with 187 | the ID. 188 | 189 | Sends one request to api.football-data.org. 190 | 191 | :param team_id: The team ID. 192 | :type team_id: integer 193 | 194 | :returns: Team: A Team object. 195 | """ 196 | endpoint = endpoints['team'].format(team_id) 197 | r = requests.get(endpoint, headers=globals.headers) 198 | globals.update_prev_response(r, endpoint) 199 | r.raise_for_status() 200 | 201 | return Team(r.json()) 202 | 203 | def get_team_players(self, team_id): 204 | """Given a team ID, returns a list of Player objects associated 205 | with the team. 206 | 207 | Sends one request to api.football-data.org. 208 | 209 | :param team_id: The team ID. 210 | :type team_id: integer 211 | 212 | :returns: player_list: A list of Player objects in the specified team. 213 | """ 214 | endpoint = endpoints['team_players'].format(team_id) 215 | r = requests.get(endpoint, headers=globals.headers) 216 | globals.update_prev_response(r, endpoint) 217 | r.raise_for_status() 218 | 219 | data = r.json() 220 | player_list = [] 221 | for player in data['players']: 222 | player_list.append(Player(player)) 223 | return player_list 224 | 225 | def get_team_fixtures(self, team_id): 226 | """Given a team ID, returns a list of Fixture objects associated 227 | with the team. 228 | 229 | Sends one request to api.football-data.org. 230 | 231 | :param team_id: The team ID. 232 | :type team_id: integer 233 | 234 | :returns: fixture_list: A list of Fixture objects for the team. 235 | """ 236 | endpoint = endpoints['team_fixtures'].format(team_id) 237 | r = requests.get(endpoint, headers=globals.headers) 238 | globals.update_prev_response(r, endpoint) 239 | r.raise_for_status() 240 | 241 | data = r.json() 242 | fixture_list = [] 243 | for fixture in data['fixtures']: 244 | fixture_list.append(Fixture(fixture)) 245 | return fixture_list 246 | 247 | def search_teams(self, team_name): 248 | """Given a team name, queries the database for matches and returns 249 | a dictionary containing key-value pairs of their team IDs and 250 | team names. 251 | 252 | Sends one request to api.football-data.org. 253 | 254 | :param team_name: The partial or full team name. 255 | :type team_name: string 256 | 257 | :returns: matches: A dict with team ID as keys and team name as values. 258 | :returns: ``None``: If no matches are found for the given team_name. 259 | """ 260 | name = team_name.replace(" ", "%20") 261 | endpoint = endpoints['team'].format('?name='+name) 262 | r = requests.get(endpoint, headers=globals.headers) 263 | globals.update_prev_response(r, endpoint) 264 | r.raise_for_status() 265 | 266 | data = r.json() 267 | if data['count'] is 0: 268 | return None 269 | else: 270 | matches = {} 271 | for team in data['teams']: 272 | matches[team['id']] = team['name'] 273 | return matches 274 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # pyfootball documentation build configuration file, created by 5 | # sphinx-quickstart on Sat Oct 15 16:50:33 2016. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | # 20 | import os 21 | import sys 22 | sys.path.insert(0, os.path.abspath('../')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | # 28 | # needs_sphinx = '1.0' 29 | 30 | import sphinx_rtd_theme 31 | 32 | # Add any Sphinx extension module names here, as strings. They can be 33 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 34 | # ones. 35 | extensions = ['sphinx.ext.autodoc'] 36 | 37 | # Add any paths that contain templates here, relative to this directory. 38 | templates_path = ['_templates'] 39 | 40 | # The suffix(es) of source filenames. 41 | # You can specify multiple suffix as a list of string: 42 | # 43 | # source_suffix = ['.rst', '.md'] 44 | source_suffix = '.rst' 45 | 46 | # The encoding of source files. 47 | # 48 | # source_encoding = 'utf-8-sig' 49 | 50 | # The master toctree document. 51 | master_doc = 'index' 52 | 53 | # General information about the project. 54 | project = 'pyfootball' 55 | copyright = '2016, Timothy "xozzo" Ng' 56 | author = 'Timothy "xozzo" Ng' 57 | 58 | # The version info for the project you're documenting, acts as replacement for 59 | # |version| and |release|, also used in various other places throughout the 60 | # built documents. 61 | # 62 | # The short X.Y version. 63 | version = '1.0' 64 | # The full version, including alpha/beta/rc tags. 65 | release = '1.0.1' 66 | 67 | # The language for content autogenerated by Sphinx. Refer to documentation 68 | # for a list of supported languages. 69 | # 70 | # This is also used if you do content translation via gettext catalogs. 71 | # Usually you set "language" from the command line for these cases. 72 | language = None 73 | 74 | # There are two options for replacing |today|: either, you set today to some 75 | # non-false value, then it is used: 76 | # 77 | # today = '' 78 | # 79 | # Else, today_fmt is used as the format for a strftime call. 80 | # 81 | # today_fmt = '%B %d, %Y' 82 | 83 | # List of patterns, relative to source directory, that match files and 84 | # directories to ignore when looking for source files. 85 | # This patterns also effect to html_static_path and html_extra_path 86 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 87 | 88 | # The reST default role (used for this markup: `text`) to use for all 89 | # documents. 90 | # 91 | # default_role = None 92 | 93 | # If true, '()' will be appended to :func: etc. cross-reference text. 94 | # 95 | # add_function_parentheses = True 96 | 97 | # If true, the current module name will be prepended to all description 98 | # unit titles (such as .. function::). 99 | # 100 | # add_module_names = True 101 | 102 | # If true, sectionauthor and moduleauthor directives will be shown in the 103 | # output. They are ignored by default. 104 | # 105 | # show_authors = False 106 | 107 | # The name of the Pygments (syntax highlighting) style to use. 108 | pygments_style = 'sphinx' 109 | 110 | # A list of ignored prefixes for module index sorting. 111 | # modindex_common_prefix = [] 112 | 113 | # If true, keep warnings as "system message" paragraphs in the built documents. 114 | # keep_warnings = False 115 | 116 | # If true, `todo` and `todoList` produce output, else they produce nothing. 117 | todo_include_todos = False 118 | 119 | 120 | # -- Options for HTML output ---------------------------------------------- 121 | 122 | # The theme to use for HTML and HTML Help pages. See the documentation for 123 | # a list of builtin themes. 124 | # 125 | html_theme = 'sphinx_rtd_theme' 126 | 127 | # Theme options are theme-specific and customize the look and feel of a theme 128 | # further. For a list of options available for each theme, see the 129 | # documentation. 130 | # 131 | # html_theme_options = {} 132 | 133 | # Add any paths that contain custom themes here, relative to this directory. 134 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 135 | 136 | # The name for this set of Sphinx documents. 137 | # " v documentation" by default. 138 | # 139 | # html_title = 'pyfootball v3.5' 140 | 141 | # A shorter title for the navigation bar. Default is the same as html_title. 142 | # 143 | # html_short_title = None 144 | 145 | # The name of an image file (relative to this directory) to place at the top 146 | # of the sidebar. 147 | # 148 | # html_logo = None 149 | 150 | # The name of an image file (relative to this directory) to use as a favicon of 151 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 152 | # pixels large. 153 | # 154 | # html_favicon = None 155 | 156 | # Add any paths that contain custom static files (such as style sheets) here, 157 | # relative to this directory. They are copied after the builtin static files, 158 | # so a file named "default.css" will overwrite the builtin "default.css". 159 | html_static_path = ['_static'] 160 | 161 | # Add any extra paths that contain custom files (such as robots.txt or 162 | # .htaccess) here, relative to this directory. These files are copied 163 | # directly to the root of the documentation. 164 | # 165 | # html_extra_path = [] 166 | 167 | # If not None, a 'Last updated on:' timestamp is inserted at every page 168 | # bottom, using the given strftime format. 169 | # The empty string is equivalent to '%b %d, %Y'. 170 | # 171 | # html_last_updated_fmt = None 172 | 173 | # If true, SmartyPants will be used to convert quotes and dashes to 174 | # typographically correct entities. 175 | # 176 | # html_use_smartypants = True 177 | 178 | # Custom sidebar templates, maps document names to template names. 179 | # 180 | # html_sidebars = {} 181 | 182 | # Additional templates that should be rendered to pages, maps page names to 183 | # template names. 184 | # 185 | # html_additional_pages = {} 186 | 187 | # If false, no module index is generated. 188 | # 189 | # html_domain_indices = True 190 | 191 | # If false, no index is generated. 192 | # 193 | # html_use_index = True 194 | 195 | # If true, the index is split into individual pages for each letter. 196 | # 197 | # html_split_index = False 198 | 199 | # If true, links to the reST sources are added to the pages. 200 | # 201 | # html_show_sourcelink = True 202 | 203 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 204 | # 205 | # html_show_sphinx = True 206 | 207 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 208 | # 209 | # html_show_copyright = True 210 | 211 | # If true, an OpenSearch description file will be output, and all pages will 212 | # contain a tag referring to it. The value of this option must be the 213 | # base URL from which the finished HTML is served. 214 | # 215 | # html_use_opensearch = '' 216 | 217 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 218 | # html_file_suffix = None 219 | 220 | # Language to be used for generating the HTML full-text search index. 221 | # Sphinx supports the following languages: 222 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' 223 | # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh' 224 | # 225 | # html_search_language = 'en' 226 | 227 | # A dictionary with options for the search language support, empty by default. 228 | # 'ja' uses this config value. 229 | # 'zh' user can custom change `jieba` dictionary path. 230 | # 231 | # html_search_options = {'type': 'default'} 232 | 233 | # The name of a javascript file (relative to the configuration directory) that 234 | # implements a search results scorer. If empty, the default will be used. 235 | # 236 | # html_search_scorer = 'scorer.js' 237 | 238 | # Output file base name for HTML help builder. 239 | htmlhelp_basename = 'pyfootballdoc' 240 | 241 | # -- Options for LaTeX output --------------------------------------------- 242 | 243 | latex_elements = { 244 | # The paper size ('letterpaper' or 'a4paper'). 245 | # 246 | # 'papersize': 'letterpaper', 247 | 248 | # The font size ('10pt', '11pt' or '12pt'). 249 | # 250 | # 'pointsize': '10pt', 251 | 252 | # Additional stuff for the LaTeX preamble. 253 | # 254 | # 'preamble': '', 255 | 256 | # Latex figure (float) alignment 257 | # 258 | # 'figure_align': 'htbp', 259 | } 260 | 261 | # Grouping the document tree into LaTeX files. List of tuples 262 | # (source start file, target name, title, 263 | # author, documentclass [howto, manual, or own class]). 264 | latex_documents = [ 265 | (master_doc, 'pyfootball.tex', 'pyfootball Documentation', 266 | 'Timothy "xozzo" Ng', 'manual'), 267 | ] 268 | 269 | # The name of an image file (relative to this directory) to place at the top of 270 | # the title page. 271 | # 272 | # latex_logo = None 273 | 274 | # For "manual" documents, if this is true, then toplevel headings are parts, 275 | # not chapters. 276 | # 277 | # latex_use_parts = False 278 | 279 | # If true, show page references after internal links. 280 | # 281 | # latex_show_pagerefs = False 282 | 283 | # If true, show URL addresses after external links. 284 | # 285 | # latex_show_urls = False 286 | 287 | # Documents to append as an appendix to all manuals. 288 | # 289 | # latex_appendices = [] 290 | 291 | # It false, will not define \strong, \code, itleref, \crossref ... but only 292 | # \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added 293 | # packages. 294 | # 295 | # latex_keep_old_macro_names = True 296 | 297 | # If false, no module index is generated. 298 | # 299 | # latex_domain_indices = True 300 | 301 | 302 | # -- Options for manual page output --------------------------------------- 303 | 304 | # One entry per manual page. List of tuples 305 | # (source start file, name, description, authors, manual section). 306 | man_pages = [ 307 | (master_doc, 'pyfootball', 'pyfootball Documentation', 308 | [author], 1) 309 | ] 310 | 311 | # If true, show URL addresses after external links. 312 | # 313 | # man_show_urls = False 314 | 315 | 316 | # -- Options for Texinfo output ------------------------------------------- 317 | 318 | # Grouping the document tree into Texinfo files. List of tuples 319 | # (source start file, target name, title, author, 320 | # dir menu entry, description, category) 321 | texinfo_documents = [ 322 | (master_doc, 'pyfootball', 'pyfootball Documentation', 323 | author, 'pyfootball', 'One line description of project.', 324 | 'Miscellaneous'), 325 | ] 326 | 327 | # Documents to append as an appendix to all manuals. 328 | # 329 | # texinfo_appendices = [] 330 | 331 | # If false, no module index is generated. 332 | # 333 | # texinfo_domain_indices = True 334 | 335 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 336 | # 337 | # texinfo_show_urls = 'footnote' 338 | 339 | # If true, do not generate a @detailmenu in the "Top" node's menu. 340 | # 341 | # texinfo_no_detailmenu = False 342 | -------------------------------------------------------------------------------- /tests/resources.py: -------------------------------------------------------------------------------- 1 | PLAYER = { 2 | "name": "Paul Pogba", 3 | "position": "Central Midfield", 4 | "jerseyNumber": 6, 5 | "dateOfBirth": "1993-03-15", 6 | "nationality": "France", 7 | "contractUntil": "2021-06-30", 8 | "marketValue": "70,000,000 €" 9 | } 10 | 11 | PLAYERS = { 12 | "_links": { 13 | "self": { 14 | "href": "http://api.football-data.org/v1/teams/5/players" 15 | }, 16 | "team": { 17 | "href": "http://api.football-data.org/v1/teams/5" 18 | } 19 | }, 20 | "count": 4, 21 | "players": [ 22 | { 23 | "name": "Manuel Neuer", 24 | "position": "Keeper", 25 | "jerseyNumber": 1, 26 | "dateOfBirth": "1986-03-27", 27 | "nationality": "Germany", 28 | "contractUntil": "2021-06-30", 29 | "marketValue": "45,000,000 €" 30 | }, 31 | { 32 | "name": "Sven Ulreich", 33 | "position": "Keeper", 34 | "jerseyNumber": 26, 35 | "dateOfBirth": "1988-08-03", 36 | "nationality": "Germany", 37 | "contractUntil": "2018-06-30", 38 | "marketValue": "2,500,000 €" 39 | }, 40 | { 41 | "name": "Tom Starke", 42 | "position": "Keeper", 43 | "jerseyNumber": 22, 44 | "dateOfBirth": "1981-03-18", 45 | "nationality": "Germany", 46 | "contractUntil": "2017-06-30", 47 | "marketValue": "200,000 €" 48 | }, 49 | { 50 | "name": "Jérôme Boateng", 51 | "position": "Centre Back", 52 | "jerseyNumber": 17, 53 | "dateOfBirth": "1988-09-03", 54 | "nationality": "Germany", 55 | "contractUntil": "2021-06-30", 56 | "marketValue": "45,000,000 €" 57 | }] 58 | } 59 | 60 | COMPETITION = { 61 | "_links": { 62 | "self": { 63 | "href": "http://api.football-data.org/v1/competitions/430" 64 | }, 65 | "teams": { 66 | "href": "http://api.football-data.org/v1/competitions/430/teams" 67 | }, 68 | "fixtures": { 69 | "href": "http://api.football-data.org/v1/competitions/430/fixtures" 70 | }, 71 | "leagueTable": { 72 | "href": "http://api.football-data.org/v1/competitions/430/leagueTable" 73 | } 74 | }, 75 | "id": 430, 76 | "caption": "1. Bundesliga 2016/17", 77 | "league": "BL1", 78 | "year": "2016", 79 | "currentMatchday": 8, 80 | "numberOfMatchdays": 34, 81 | "numberOfTeams": 18, 82 | "numberOfGames": 306, 83 | "lastUpdated": "2016-10-19T07:00:05Z" 84 | } 85 | 86 | COMP_TEAMS = { 87 | "_links": { 88 | "self": { 89 | "href": "http://api.football-data.org/v1/competitions/430/teams" 90 | }, 91 | "competition": { 92 | "href": "http://api.football-data.org/v1/competitions/430" 93 | } 94 | }, 95 | "count": 18, 96 | "teams": [ 97 | { 98 | "_links": { 99 | "self": { 100 | "href": "http://api.football-data.org/v1/teams/5" 101 | }, 102 | "fixtures": { 103 | "href": "http://api.football-data.org/v1/teams/5/fixtures" 104 | }, 105 | "players": { 106 | "href": "http://api.football-data.org/v1/teams/5/players" 107 | } 108 | }, 109 | "name": "FC Bayern München", 110 | "code": "FCB", 111 | "shortName": "Bayern", 112 | "squadMarketValue": "582,225,000 €", 113 | "crestUrl": "http://upload.wikimedia.org/wikipedia/commons/c/c5/Logo_FC_Bayern_München.svg" 114 | }, 115 | { 116 | "_links": { 117 | "self": { 118 | "href": "http://api.football-data.org/v1/teams/12" 119 | }, 120 | "fixtures": { 121 | "href": "http://api.football-data.org/v1/teams/12/fixtures" 122 | }, 123 | "players": { 124 | "href": "http://api.football-data.org/v1/teams/12/players" 125 | } 126 | }, 127 | "name": "Werder Bremen", 128 | "code": "SVW", 129 | "shortName": "Bremen", 130 | "squadMarketValue": "67,750,000 €", 131 | "crestUrl": "http://upload.wikimedia.org/wikipedia/commons/b/be/SV-Werder-Bremen-Logo.svg" 132 | }, 133 | { 134 | "_links": { 135 | "self": { 136 | "href": "http://api.football-data.org/v1/teams/16" 137 | }, 138 | "fixtures": { 139 | "href": "http://api.football-data.org/v1/teams/16/fixtures" 140 | }, 141 | "players": { 142 | "href": "http://api.football-data.org/v1/teams/16/players" 143 | } 144 | }, 145 | "name": "FC Augsburg", 146 | "code": "FCA", 147 | "shortName": "Augsburg", 148 | "squadMarketValue": "61,200,000 €", 149 | "crestUrl": "http://upload.wikimedia.org/wikipedia/de/b/b5/Logo_FC_Augsburg.svg" 150 | }] 151 | } 152 | 153 | TEAM = { 154 | "_links": { 155 | "self": { 156 | "href": "http://api.football-data.org/v1/teams/5" 157 | }, 158 | "fixtures": { 159 | "href": "http://api.football-data.org/v1/teams/5/fixtures" 160 | }, 161 | "players": { 162 | "href": "http://api.football-data.org/v1/teams/5/players" 163 | } 164 | }, 165 | "name": "FC Bayern München", 166 | "code": "FCB", 167 | "shortName": "Bayern", 168 | "squadMarketValue": "582,225,000 €", 169 | "crestUrl": "http://upload.wikimedia.org/wikipedia/commons/c/c5/Logo_FC_Bayern_München.svg" 170 | } 171 | 172 | FIXTURE = { 173 | "_links": { 174 | "self": { 175 | "href": "http://api.football-data.org/v1/fixtures/152247" 176 | }, 177 | "competition": { 178 | "href": "http://api.football-data.org/v1/competitions/430" 179 | }, 180 | "homeTeam": { 181 | "href": "http://api.football-data.org/v1/teams/6" 182 | }, 183 | "awayTeam": { 184 | "href": "http://api.football-data.org/v1/teams/5" 185 | } 186 | }, 187 | "date": "2016-09-09T18:30:00Z", 188 | "status": "FINISHED", 189 | "matchday": 2, 190 | "homeTeamName": "FC Schalke 04", 191 | "awayTeamName": "FC Bayern München", 192 | "result": { 193 | "goalsHomeTeam": 0, 194 | "goalsAwayTeam": 2 195 | }, 196 | "odds": None 197 | } 198 | 199 | FIXTURE_WITH_HT_AND_ODDS = { 200 | "_links": { 201 | "self": { 202 | "href": "http://api.football-data.org/v1/fixtures/149855" 203 | }, 204 | "competition": { 205 | "href": "http://api.football-data.org/v1/competitions/424" 206 | }, 207 | "homeTeam": { 208 | "href": "http://api.football-data.org/v1/teams/773" 209 | }, 210 | "awayTeam": { 211 | "href": "http://api.football-data.org/v1/teams/811" 212 | } 213 | }, 214 | "date": "2016-06-10T19:00:00Z", 215 | "status": "FINISHED", 216 | "matchday": 1, 217 | "homeTeamName": "France", 218 | "awayTeamName": "Romania", 219 | "result": { 220 | "goalsHomeTeam": 2, 221 | "goalsAwayTeam": 1, 222 | "halfTime": { 223 | "goalsHomeTeam": 0, 224 | "goalsAwayTeam": 0 225 | } 226 | }, 227 | "odds": { 228 | "homeWin": 56, 229 | "draw": 15, 230 | "awayWin": 1.03 231 | } 232 | } 233 | 234 | FIXTURES = { 235 | "_links": { 236 | "self": { 237 | "href": "http://api.football-data.org/v1/teams/5/fixtures" 238 | }, 239 | "team": { 240 | "href": "http://api.football-data.org/v1/teams/5" 241 | } 242 | }, 243 | "count": 3, 244 | "fixtures": [ 245 | { 246 | "_links": { 247 | "self": { 248 | "href": "http://api.football-data.org/v1/fixtures/153633" 249 | }, 250 | "competition": { 251 | "href": "http://api.football-data.org/v1/competitions/432" 252 | }, 253 | "homeTeam": { 254 | "href": "http://api.football-data.org/v1/teams/49" 255 | }, 256 | "awayTeam": { 257 | "href": "http://api.football-data.org/v1/teams/5" 258 | } 259 | }, 260 | "date": "2016-08-19T18:45:00Z", 261 | "status": "FINISHED", 262 | "matchday": 1, 263 | "homeTeamName": "FC Carl Zeiss Jena", 264 | "awayTeamName": "FC Bayern München", 265 | "result": { 266 | "goalsHomeTeam": 0, 267 | "goalsAwayTeam": 5 268 | }, 269 | "odds": { 270 | "homeWin": 56, 271 | "draw": 15, 272 | "awayWin": 1.03 273 | } 274 | }, 275 | { 276 | "_links": { 277 | "self": { 278 | "href": "http://api.football-data.org/v1/fixtures/152258" 279 | }, 280 | "competition": { 281 | "href": "http://api.football-data.org/v1/competitions/430" 282 | }, 283 | "homeTeam": { 284 | "href": "http://api.football-data.org/v1/teams/5" 285 | }, 286 | "awayTeam": { 287 | "href": "http://api.football-data.org/v1/teams/12" 288 | } 289 | }, 290 | "date": "2016-08-26T18:30:00Z", 291 | "status": "FINISHED", 292 | "matchday": 1, 293 | "homeTeamName": "FC Bayern München", 294 | "awayTeamName": "Werder Bremen", 295 | "result": { 296 | "goalsHomeTeam": 6, 297 | "goalsAwayTeam": 0 298 | }, 299 | "odds": { 300 | "homeWin": 1.12, 301 | "draw": 8, 302 | "awayWin": 26 303 | } 304 | }, 305 | { 306 | "_links": { 307 | "self": { 308 | "href": "http://api.football-data.org/v1/fixtures/152247" 309 | }, 310 | "competition": { 311 | "href": "http://api.football-data.org/v1/competitions/430" 312 | }, 313 | "homeTeam": { 314 | "href": "http://api.football-data.org/v1/teams/6" 315 | }, 316 | "awayTeam": { 317 | "href": "http://api.football-data.org/v1/teams/5" 318 | } 319 | }, 320 | "date": "2016-09-09T18:30:00Z", 321 | "status": "FINISHED", 322 | "matchday": 2, 323 | "homeTeamName": "FC Schalke 04", 324 | "awayTeamName": "FC Bayern München", 325 | "result": { 326 | "goalsHomeTeam": 0, 327 | "goalsAwayTeam": 2 328 | }, 329 | "odds": { 330 | "homeWin": 12, 331 | "draw": 6, 332 | "awayWin": 1.3 333 | } 334 | }] 335 | } 336 | 337 | LEAGUE_TABLE = { 338 | "_links": { 339 | "self": { 340 | "href": "http://api.football-data.org/v1/competitions/430/leagueTable/?matchday=7" 341 | }, 342 | "competition": { 343 | "href": "http://api.football-data.org/v1/competitions/430" 344 | } 345 | }, 346 | "leagueCaption": "1. Bundesliga 2016/17", 347 | "matchday": 7, 348 | "standing": [ 349 | { 350 | "_links": { 351 | "team": { 352 | "href": "http://api.football-data.org/v1/teams/5" 353 | } 354 | }, 355 | "position": 1, 356 | "teamName": "FC Bayern München", 357 | "crestURI": "http://upload.wikimedia.org/wikipedia/commons/c/c5/Logo_FC_Bayern_München.svg", 358 | "playedGames": 7, 359 | "points": 17, 360 | "goals": 18, 361 | "goalsAgainst": 4, 362 | "goalDifference": 14, 363 | "wins": 5, 364 | "draws": 2, 365 | "losses": 0, 366 | "home": { 367 | "goals": 13, 368 | "goalsAgainst": 2, 369 | "wins": 3, 370 | "draws": 1, 371 | "losses": 0 372 | }, 373 | "away": { 374 | "goals": 5, 375 | "goalsAgainst": 2, 376 | "wins": 2, 377 | "draws": 1, 378 | "losses": 0 379 | } 380 | }, 381 | { 382 | "_links": { 383 | "team": { 384 | "href": "http://api.football-data.org/v1/teams/1" 385 | } 386 | }, 387 | "position": 2, 388 | "teamName": "1. FC Köln", 389 | "crestURI": "http://upload.wikimedia.org/wikipedia/de/1/16/1._FC_Köln.svg", 390 | "playedGames": 7, 391 | "points": 15, 392 | "goals": 12, 393 | "goalsAgainst": 4, 394 | "goalDifference": 8, 395 | "wins": 4, 396 | "draws": 3, 397 | "losses": 0, 398 | "home": { 399 | "goals": 8, 400 | "goalsAgainst": 2, 401 | "wins": 3, 402 | "draws": 1, 403 | "losses": 0 404 | }, 405 | "away": { 406 | "goals": 4, 407 | "goalsAgainst": 2, 408 | "wins": 1, 409 | "draws": 2, 410 | "losses": 0 411 | } 412 | }, 413 | { 414 | "_links": { 415 | "team": { 416 | "href": "http://api.football-data.org/v1/teams/721" 417 | } 418 | }, 419 | "position": 3, 420 | "teamName": "Red Bull Leipzig", 421 | "crestURI": "http://upload.wikimedia.org/wikipedia/de/d/d4/RB_Leipzig_2010_logo.svg", 422 | "playedGames": 7, 423 | "points": 15, 424 | "goals": 12, 425 | "goalsAgainst": 5, 426 | "goalDifference": 7, 427 | "wins": 4, 428 | "draws": 3, 429 | "losses": 0, 430 | "home": { 431 | "goals": 4, 432 | "goalsAgainst": 2, 433 | "wins": 2, 434 | "draws": 1, 435 | "losses": 0 436 | }, 437 | "away": { 438 | "goals": 8, 439 | "goalsAgainst": 3, 440 | "wins": 2, 441 | "draws": 2, 442 | "losses": 0 443 | } 444 | }] 445 | } --------------------------------------------------------------------------------