Skip to content

Commit

Permalink
Most definitely a first commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
aaront committed Mar 21, 2016
0 parents commit 5d9591b
Show file tree
Hide file tree
Showing 16 changed files with 353 additions and 0 deletions.
61 changes: 61 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Created by .ignore support plugin (hsz.mobi)
### Python template
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover

# Translations
*.mo
*.pot

# Django stuff:
*.log

# Sphinx documentation
docs/_build/

# PyBuilder
target/

13 changes: 13 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Copyright 2016 Aaron Toth

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
6 changes: 6 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
puckdb
======

An async-first hockey data extractor and API.

Still under active development and not ready for consumption.
3 changes: 3 additions & 0 deletions puckdb/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
__title__ = 'puckdb'
__author__ = 'Aaron Toth'
__version__ = '0.0.1'
Empty file added puckdb/async/__init__.py
Empty file.
14 changes: 14 additions & 0 deletions puckdb/async/db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from contextlib import contextmanager

from aiopg.sa import create_engine

from .. import conf


@contextmanager
async def get_engine():
async with create_engine(dsn=conf.get_db()) as engine:
await engine


__all__ = ['get_engine']
53 changes: 53 additions & 0 deletions puckdb/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import configparser
import os

import click

from . import __title__


def _get_config_file_path():
return os.path.join(click.get_app_dir(__title__), 'config.ini')


def _read():
conf_file = _get_config_file_path()
conf = configparser.RawConfigParser()
try:
conf.read(conf_file)
return conf, conf_file
except IOError:
raise IOError('Could not find settings file.\n'
'Make sure it exists at "{path}"'.format(path=conf_file))


def _write(dsn=''):
conf_file = _get_config_file_path()
conf = configparser.RawConfigParser()
if 'db' not in conf.sections():
conf.add_section('db')
conf.set('db', 'dsn', dsn)
with open(conf_file, 'w') as f:
conf.write(f)


def init():
config_file = _get_config_file_path()
if not os.path.exists(os.path.dirname(config_file)):
os.makedirs(os.path.dirname(config_file))
_write()


def get_db() -> str:
dsn = os.getenv('PUCKDB_DATABASE', None)
if dsn:
return dsn
config, config_file = _read()
try:
return config.get('db', 'dsn')
except configparser.NoSectionError:
raise IOError('Could not read database settings from the config file.\n'
'Make sure the [db] section exists in "{path}"'.format(path=config_file))
except configparser.NoOptionError:
raise IOError('Could not read database settings from the config file.\n'
'Make sure the [db] has the proper headings in "{path}"'.format(path=config_file))
17 changes: 17 additions & 0 deletions puckdb/console.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import click


@click.command()
def init():
pass


@click.group
@click.version_option()
def main():
pass

main.add_command(init)

if __name__ == '__main__':
main()
11 changes: 11 additions & 0 deletions puckdb/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import enum


class GameState(enum.Enum):
not_started = -1
in_progress = 0
finished = 1


class GameEvent(enum.Enum):
pass
33 changes: 33 additions & 0 deletions puckdb/db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import sqlalchemy as sa
from sqlalchemy.schema import CreateTable

from .constants import GameState
from .async.db import *

metadata = sa.MetaData()

league_tbl = sa.Table('league', metadata,
sa.Column('id', sa.SmallInteger, primary_key=True),
sa.Column('name', sa.String(255))
)

team_tbl = sa.Table('team', metadata,
sa.Column('id', sa.SmallInteger, primary_key=True),
sa.Column('league', sa.SmallInteger, sa.ForeignKey('league.id'), nullable=False),
sa.Column('name', sa.String),
sa.Column('full_name', sa.String),
sa.Column('city', sa.String)
)

game_tbl = sa.Table('game', metadata,
sa.Column('id', sa.BigInteger, primary_key=True),
sa.Column('season', sa.SmallInteger),
sa.Column('status', sa.Enum(GameState)),
sa.Column('away', sa.SmallInteger, sa.ForeignKey('team.id'), nullable=False),
sa.Column('home', sa.SmallInteger, sa.ForeignKey('team.id'), nullable=False),
sa.Column('away_score', sa.SmallInteger),
sa.Column('home_score', sa.SmallInteger),
sa.Column('start', sa.DateTime, index=True),
sa.Column('duration', sa.Time),
sa.Column('periods', sa.SmallInteger)
)
6 changes: 6 additions & 0 deletions puckdb/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class FilterException(Exception):
def __init__(self, message=None):
self.message = message

def __str__(self):
return 'Invalid filter{message}'.format(': ' + self.message if self.message else '')
40 changes: 40 additions & 0 deletions puckdb/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from datetime import datetime

from . import exceptions


class BaseFilter(object):
pass


class TeamFilter(BaseFilter):
def __init__(self, name=None):
self.name = name


class GameFilter(BaseFilter):
def __init__(self, from_date=None, to_date=None, team=None):
"""
:type from_date: datetime
:type to_date: datetime
:type team: TeamFilter
"""
if from_date is None:
raise exceptions.FilterException('from_date must be provided')
to_date = to_date or datetime.utcnow()
if to_date < from_date:
raise exceptions.FilterException('to_date must be after from_date')
self.from_date = from_date
self.to_date = to_date
self.team = team

@property
def season_range(self):
seasons = []
from_season = self.from_date.year if self.from_date.month >= 9 else self.from_date.year - 1
to_season = self.to_date.year - 1 if self.to_date.month < 9 else self.to_date.year
for i in range(to_season - from_season + 1):
season_start = from_season + i
seasons.append('{}{}'.format(season_start, season_start+1))
return seasons
20 changes: 20 additions & 0 deletions puckdb/zamboni.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import abc
from datetime import datetime

import aiohttp

from . import exceptions, filters

class BaseScraper(object):
__metaclass__ = abc.ABCMeta

@abc.abstractmethod
async def get_schedule(self, game_filter: filters.GameFilter):
pass


class NHLScraper(BaseScraper):
schedule_url = 'http://live.nhl.com/GameData/SeasonSchedule-{season}.json'

async def get_schedule(self, game_filter: filters.GameFilter):
pass
44 changes: 44 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import re
import ast

from setuptools import setup, find_packages

_version_re = re.compile(r'__version__\s+=\s+(.*)')
with open('puckdb/__init__.py', 'rb') as f:
version = str(ast.literal_eval(_version_re.search(
f.read().decode('utf-8')).group(1)))

setup(
name='puckdb',
author='Aaron Toth',
version=version,
url='https://github.com/aaront/puckdb',
description='An async-first hockey data extractor and API',
long_description=open('README.rst').read(),
install_requires=[
'click',
'cchardet',
'aiohttp',
'aiopg',
'sqlalchemy'
],
test_suite="tests",
include_package_data=True,
packages=find_packages(),
package_data={'': ['LICENSE']},
package_dir={'puckdb': 'puckdb'},
license='Apache 2.0',
entry_points='''
[console_scripts]
puckdb=puckdb.console:main
''',
classifiers=(
'Development Status :: 2 - Pre-Alpha',
'Intended Audience :: Developers',
'Natural Language :: English',
'License :: OSI Approved :: Apache Software License',
'Programming Language :: Python',
'Programming Language :: Python :: 3.5',
'Topic :: Software Development :: Libraries'
)
)
Empty file added tests/__init__.py
Empty file.
32 changes: 32 additions & 0 deletions tests/test_filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import unittest
from datetime import datetime

from puckdb import filters


class TestGameFilter(unittest.TestCase):
def test_one_season_range(self):
from_date = datetime(2014, 10, 22)
to_date = datetime(2015, 4, 1)
game_filter = filters.GameFilter(from_date=from_date, to_date=to_date)
seasons = game_filter.season_range
self.assertEqual(1, len(seasons))
self.assertEqual('20142015', seasons[0])

def test_season_before_range(self):
from_date = datetime(2014, 4, 22)
to_date = datetime(2015, 4, 1)
game_filter = filters.GameFilter(from_date=from_date, to_date=to_date)
seasons = game_filter.season_range
self.assertEqual(2, len(seasons))
self.assertEqual('20132014', seasons[0])
self.assertEqual('20142015', seasons[1])

def test_season_after_range(self):
from_date = datetime(2014, 10, 22)
to_date = datetime(2015, 9, 1)
game_filter = filters.GameFilter(from_date=from_date, to_date=to_date)
seasons = game_filter.season_range
self.assertEqual(2, len(seasons))
self.assertEqual('20142015', seasons[0])
self.assertEqual('20152016', seasons[1])

0 comments on commit 5d9591b

Please sign in to comment.