Skip to content

Commit

Permalink
Start building out commands and extension/dialect form. Also some sma…
Browse files Browse the repository at this point in the history
…ll tweaks.
  • Loading branch information
gwax committed Jun 30, 2016
1 parent 05da7c8 commit 20cd33e
Show file tree
Hide file tree
Showing 9 changed files with 223 additions and 92 deletions.
6 changes: 5 additions & 1 deletion check.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/bin/bash

CI="$CI"

set -e
set -o nounset

Expand All @@ -11,7 +13,9 @@ EXIT=0
if [ "$SUITE" = "test" ]; then
echo "Running test suite"
py.test --cov=mtg_ssm --strict -r w tests || EXIT=$?
coveralls || EXIT=$?
if [ "$CI" = "true" ]; then
coveralls || EXIT=$?
fi

elif [ "$SUITE" = "lint" ]; then
echo "Running lint suite"
Expand Down
40 changes: 22 additions & 18 deletions mtg_ssm/mtgjson.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,27 +40,31 @@ def fetch_mtgjson(data_path):
local_version = (0, 0, 0)

try:
print('Checking remove vs local version of mtgjson data.')
ver_req = requests.get(MTGJSON_ADDRESS + VERSION_FILENAME)
ver_req.raise_for_status()

remote_version_data = ver_req.json()
remote_version = tuple(
int(v) for v in remote_version_data['version'].split('.'))

if local_version >= remote_version:
print('Mtgjson data is already up to date.')
return False

print('Downloading mtgjson data.')
allsets_filename = os.path.join(data_path, ALLSETS_FILENAME)
mtg_req = requests.get(MTGJSON_ADDRESS + ALLSETS_FILENAME)
mtg_req.raise_for_status()
with open(allsets_filename, 'wb') as allsets_file:
allsets_file.write(mtg_req.content)
with open(version_filename, 'wb') as version_file:
version_file.write(ver_req.content)
return True
except requests.ConnectionError as err:
raise DownloadError('Could not connect to mtgjson') from err
if ver_req.status_code != 200:
raise DownloadError(
'Could not fetch version: {}'.format(ver_req.reason))
remote_version_data = ver_req.json()
remote_version = tuple(
int(v) for v in remote_version_data['version'].split('.'))

if local_version >= remote_version:
return False

print('Downloading mtgjson data.')
allsets_filename = os.path.join(data_path, ALLSETS_FILENAME)
mtg_req = requests.get(MTGJSON_ADDRESS + ALLSETS_FILENAME)
with open(allsets_filename, 'wb') as allsets_file:
allsets_file.write(mtg_req.content)
with open(version_filename, 'wb') as version_file:
version_file.write(ver_req.content)
return True
except requests.exceptions.HTTPError as err:
raise DownloadError('Could not retrieve mtgjson data') from err


def read_mtgjson(data_path):
Expand Down
29 changes: 20 additions & 9 deletions mtg_ssm/serialization/csv.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
] + [ct.name for ct in counts.CountTypes]


def row_for_printing(printing, print_counts):
def row_for_printing(printing, printing_counts):
"""Given a CardPrinting and counts, return a csv row."""
csv_row = {
'set': printing.set_code,
Expand All @@ -23,35 +23,46 @@ def row_for_printing(printing, print_counts):
'multiverseid': printing.multiverseid,
'id': printing.id_,
}
for counttype, count in print_counts.get(printing, {}).items():
for counttype, count in printing_counts.items():
if count:
csv_row[counttype.name] = count
return csv_row


def rows_for_printings(cdb, print_counts):
def rows_for_printings(cdb, print_counts, verbose):
"""Generator that yields csv rows from a card_db."""
for card_set in cdb.card_sets:
for printing in card_set.printings:
yield row_for_printing(printing, print_counts)
printing_counts = print_counts.get(printing, {})
if verbose or any(printing_counts):
yield row_for_printing(printing, printing_counts)


class MtgCsvSerializer(interface.MtgSsmSerializer):
"""MtgSsmSerializer for reading and writing csv files."""

format = 'csv'
class CsvFullDialect(interface.SerializationDialect):
"""csv collection writing a row for every printing"""
extension = 'csv'
dialect = 'csv'

verbose = True

def write(self, filename: str, print_counts) -> None:
"""Write print counts to a file."""
with open(filename, 'w') as csv_file:
writer = csv.DictWriter(csv_file, CSV_HEADER)
writer.writeheader()
for row in rows_for_printings(self.cdb, print_counts):
for row in rows_for_printings(
self.cdb, print_counts, self.verbose):
writer.writerow(row)

def read(self, filename: str):
"""Read print counts from file."""
with open(filename, 'r') as csv_file:
return counts.aggregate_print_counts(
self.cdb, csv.DictReader(csv_file))


class CsvTerseDialect(CsvFullDialect):
"""csv collection writing only rows that have counts"""
dialect = 'terse'

verbose = False
25 changes: 12 additions & 13 deletions mtg_ssm/serialization/deckbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def rows_for_printing(printing, print_counts):
yield foils_row


def deckbox_rows_from_print_counts(cdb, print_counts):
def dbox_rows_from_print_counts(cdb, print_counts):
"""Generator that yields csv rows from a card_db."""
for card_set in cdb.card_sets:
for printing in card_set.printings:
Expand All @@ -118,34 +118,33 @@ def get_mtgj_setname(edition, number):
return setname


def create_card_row(cdb, deckbox_row):
def create_card_row(cdb, dbox_row):
"""Given a row from a deckbox csv file, return a counts row."""
edition = deckbox_row['Edition']
number = deckbox_row['Card Number']
edition = dbox_row['Edition']
number = dbox_row['Card Number']
mtgj_setname = get_mtgj_setname(edition, number)
set_code = cdb.setname_to_card_set[mtgj_setname].code
row_counts = int(deckbox_row['Count']) + int(deckbox_row['Tradelist Count'])
countname = 'foils' if deckbox_row['Foil'] == 'foil' else 'copies'
row_counts = int(dbox_row['Count']) + int(dbox_row['Tradelist Count'])
countname = 'foils' if dbox_row['Foil'] == 'foil' else 'copies'
return {
'name': deckbox_row['Name'].split('//')[0].strip(),
'name': dbox_row['Name'].split('//')[0].strip(),
'set': set_code,
'number': number,
countname: row_counts,
}


class MtgDeckboxSerializer(interface.MtgSsmSerializer):
"""MtgSsmSerializer for reading/writing deckbox compatible csv files."""

format = 'deckbox'
extension = None
class MtgDeckboxSerializer(interface.SerializationDialect):
"""csv collection compatible with deckbox csv import/export"""
extension = 'csv'
dialect = 'deckbox'

def write(self, filename: str, print_counts) -> None:
"""Write print counts to a deckbox csv file."""
with open(filename, 'w') as csv_file:
writer = csv.DictWriter(csv_file, DECKBOX_HEADER)
writer.writeheader()
for row in deckbox_rows_from_print_counts(self.cdb, print_counts):
for row in dbox_rows_from_print_counts(self.cdb, print_counts):
writer.writerow(row)

def read(self, filename: str):
Expand Down
100 changes: 54 additions & 46 deletions mtg_ssm/serialization/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,37 @@ class Error(Exception):
"""Base error for serializers."""


class InvalidExtensionOrFormat(Error):
"""Raised if an invalid extension or format is provided."""
class UnknownDialect(Exception):
"""Raised when an (extension, dialect) pair is requested."""


class DeserializationError(Error):
"""Raised when there is an error reading counts from a file."""


class MtgSsmSerializer(metaclass=abc.ABCMeta):
"""Abstract interface for a mtg ssm serializer."""
class SerializationDialect(metaclass=abc.ABCMeta):
"""Abstract interface for mtg ssm serialization dialect."""

format = None # type: str
extension = None # type: str

_format_to_serializer = None
_extension_to_serializer = None
_dialects = None
_dialect_registry = None

def __init__(self, cdb: card_db.CardDb) -> None:
self.cdb = cdb

@property
@abc.abstractmethod
def extension(self) -> str:
"""Registered file extension, excluding '.' """

@property
@abc.abstractmethod
def dialect(self) -> str:
"""Registered dialect name.
Note: a dialect that matches the extension will be considered the
default dialect for that extension.
"""

@abc.abstractmethod
def write(self, filename: str, print_counts) -> None:
"""Write print counts to a file."""
Expand All @@ -42,41 +53,38 @@ def read(self, filename: str) -> Dict[
models.CardPrinting, Dict[counts.CountTypes, int]]:
"""Read print counts from file."""

@classmethod
def _register_subclasses(cls):
"""Register formats and extensions for all subclasses."""
cls._format_to_serializer = {}
cls._extension_to_serializer = {}
subclasses = collections.deque(cls.__subclasses__())
@staticmethod
def _register_dialects():
"""Register dialects for extensions from all subclasses."""
dialects = SerializationDialect._dialects = []
registry = SerializationDialect._dialect_registry = {}
subclasses = collections.deque(SerializationDialect.__subclasses__())
while subclasses:
subclass = subclasses.popleft()
if subclass.format is not None:
cls._format_to_serializer[subclass.format] = subclass
if subclass.extension is not None:
cls._extension_to_serializer[subclass.extension] = subclass
subclasses.extend(subclass.__subclasses__())

@classmethod
def all_formats(cls):
"""List of all valid serializer formats."""
if cls._format_to_serializer is None:
cls._register_subclasses()
formats = ['auto']
formats.extend(cls._format_to_serializer)
return formats

@classmethod
def by_extension_and_format(cls, extension: str, ser_format: str):
"""Get the appropriate serialzer for a file."""
if cls._format_to_serializer is None:
cls._register_subclasses()
if ser_format == 'auto':
serializer = cls._extension_to_serializer.get(extension.lstrip('.'))
else:
serializer = cls._format_to_serializer.get(ser_format)

if serializer is None:
raise InvalidExtensionOrFormat(
'Cannot find serializer for format: %s and extension %s' % (
ser_format, extension))
return serializer
klass = subclasses.popleft()
print(klass)
if not klass.__abstractmethods__:
print(klass.extension, klass.dialect)
registry[(klass.extension, klass.dialect)] = klass
dialects.append(
(klass.extension, klass.dialect, klass.__doc__))
subclasses.extend(klass.__subclasses__())
dialects.sort()

@staticmethod
def dialects():
"""List of (extension, dialect, description) of registered dialects."""
if SerializationDialect._dialects is None:
SerializationDialect._register_dialects()
return SerializationDialect._dialects

@staticmethod
def by_extension(extension, dialect_mappings):
"""Get a serializer class for a given extension and dialect mapping."""
if SerializationDialect._dialect_registry is None:
SerializationDialect._register_dialects()
dialect = dialect_mappings.get(extension, extension)
try:
return SerializationDialect._dialect_registry[(extension, dialect)]
except KeyError:
raise UnknownDialect(
'Extension: {ext} dialect: {dia} not found in registry')
7 changes: 3 additions & 4 deletions mtg_ssm/serialization/xlsx.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,11 +201,10 @@ def counts_from_sheet(sheet):
yield dict(zip(header, row_values))


class MtgXlsxSerializer(interface.MtgSsmSerializer):
"""MtgSsmSerializer for reading and writing xlsx files."""

format = 'xlsx'
class MtgXlsxSerializer(interface.SerializationDialect):
"""excel xlsx collection"""
extension = 'xlsx'
dialect = 'xlsx'

def write(self, filename: str, print_counts) -> None:
"""Write print counts to an xlsx file."""
Expand Down
Loading

0 comments on commit 20cd33e

Please sign in to comment.