Add UI for want lists.

This commit is contained in:
luxick
2017-05-23 18:38:25 +02:00
parent 7adba9194f
commit fdcaac6e21
13 changed files with 344 additions and 39 deletions

View File

@@ -0,0 +1 @@
from cardvault import application

View File

@@ -18,19 +18,19 @@ from cardvault import handlers
from cardvault import util from cardvault import util
from cardvault import search_funct from cardvault import search_funct
from cardvault import lib_funct from cardvault import lib_funct
from cardvault import wants_funct
class Application: class Application:
# ---------------------------------Initialize the Application---------------------------------------------- # ---------------------------------Initialize the Application----------------------------------------------
def __init__(self): def __init__(self):
# Load ui files # Load ui files
self.ui = Gtk.Builder() self.ui = Gtk.Builder()
self.ui.add_from_file(util.get_ui_filename("mainwindow.glade")) self.ui.add_from_file(util.get_ui_filename("mainwindow.glade"))
self.ui.add_from_file(util.get_ui_filename("overlays.glade")) self.ui.add_from_file(util.get_ui_filename("overlays.glade"))
self.ui.add_from_file(util.get_ui_filename("search.glade")) self.ui.add_from_file(util.get_ui_filename("search.glade"))
self.ui.add_from_file(util.get_ui_filename("library.glade")) self.ui.add_from_file(util.get_ui_filename("library.glade"))
self.ui.add_from_file(util.get_ui_filename("wants.glade"))
self.current_page = None self.current_page = None
self.unsaved_changes = False self.unsaved_changes = False
@@ -40,7 +40,8 @@ class Application:
self.pages = { self.pages = {
"search": self.ui.get_object("searchView"), "search": self.ui.get_object("searchView"),
"library": self.ui.get_object("libraryView"), "library": self.ui.get_object("libraryView"),
"decks": not_found "decks": not_found,
"wants": self.ui.get_object("wantsView")
} }
# Load configuration file # Load configuration file
@@ -58,15 +59,20 @@ class Application:
self.library = None self.library = None
self.tags = None self.tags = None
self.wants = None
self.load_library() self.load_library()
self.handlers = handlers.Handlers(self) self.handlers = handlers.Handlers(self)
self.ui.connect_signals(self.handlers) self.ui.connect_signals(self.handlers)
# Inizialize the views
search_funct.init_search_view(self) search_funct.init_search_view(self)
lib_funct.init_library_view(self) lib_funct.init_library_view(self)
wants_funct.init_wants_view(self)
self.ui.get_object("mainWindow").connect('delete-event', Gtk.main_quit) self.ui.get_object("mainWindow").connect('delete-event', Gtk.main_quit)
self.ui.get_object("mainWindow").show_all() self.ui.get_object("mainWindow").show_all()
self.push_status("Card Vault ready.") self.push_status("Card Vault ready.")
@@ -206,16 +212,26 @@ class Application:
def load_library(self): def load_library(self):
all_existing = True all_existing = True
# Load library file # Load library file
self.library = util.load_file(util.get_root_filename("library")) self.library = util.load_file(util.get_root_filename("library"))
if not self.library: if not self.library:
all_existing = False all_existing = False
self.library = {} self.library = {}
# Load tags file # Load tags file
self.tags = util.load_file(util.get_root_filename("tags")) self.tags = util.load_file(util.get_root_filename("tags"))
if not self.tags: if not self.tags:
all_existing = False all_existing = False
self.tags = {} self.tags = {}
# Load wants lists
self.wants = util.load_file(util.get_root_filename("wants"))
if not self.wants:
all_existing = False
self.wants = {}
# If parts were missing save to create the files
if not all_existing: if not all_existing:
self.save_library() self.save_library()
self.push_status("Library loaded") self.push_status("Library loaded")
@@ -267,6 +283,12 @@ class Application:
util.log("Tag '" + old + "' renamed to '" + new + "'", util.LogLevel.Info) util.log("Tag '" + old + "' renamed to '" + new + "'", util.LogLevel.Info)
self.unsaved_changes = True self.unsaved_changes = True
def add_want_list(self, name):
self.wants[name] = {}
util.log("Want list '" + name + "' created", util.LogLevel.Info)
self.push_status("Created want list '" + name + "'")
self.unsaved_changes = True
def add_card_to_lib(self, card, tag=None): def add_card_to_lib(self, card, tag=None):
if tag is not None: if tag is not None:
self.tag_card(card, tag) self.tag_card(card, tag)

View File

@@ -6,6 +6,14 @@
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<signal name="show" handler="lib_tree_popup_showed" swapped="no"/> <signal name="show" handler="lib_tree_popup_showed" swapped="no"/>
<child>
<object class="GtkMenuItem" id="tagItem">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Tag Card</property>
<property name="use_underline">True</property>
</object>
</child>
<child> <child>
<object class="GtkMenuItem" id="untagItem"> <object class="GtkMenuItem" id="untagItem">
<property name="visible">True</property> <property name="visible">True</property>

View File

@@ -138,6 +138,19 @@
<accelerator key="3" signal="activate" modifiers="GDK_CONTROL_MASK"/> <accelerator key="3" signal="activate" modifiers="GDK_CONTROL_MASK"/>
</object> </object>
</child> </child>
<child>
<object class="GtkRadioMenuItem" id="wantsViewItem">
<property name="name">wants</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Wants</property>
<property name="use_underline">True</property>
<property name="draw_as_radio">True</property>
<property name="group">searchViewItem</property>
<signal name="activate" handler="on_view_changed" swapped="no"/>
<accelerator key="4" signal="activate" modifiers="GDK_CONTROL_MASK"/>
</object>
</child>
</object> </object>
</child> </child>
</object> </object>

View File

@@ -225,4 +225,57 @@
</packing> </packing>
</child> </child>
</object> </object>
<object class="GtkGrid" id="wantsOverlay">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="orientation">vertical</property>
<property name="row_spacing">10</property>
<property name="column_homogeneous">True</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="no_show_all">True</property>
<property name="valign">end</property>
<property name="pixel_size">100</property>
<property name="icon_name">edit-find-symbolic</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="no_show_all">True</property>
<property name="valign">center</property>
<property name="label" translatable="yes">No Results</property>
<attributes>
<attribute name="weight" value="bold"/>
<attribute name="scale" value="2"/>
</attributes>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="no_show_all">True</property>
<property name="valign">start</property>
<property name="label" translatable="yes">Use the Search function to add cards to your wants</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
</object>
</interface> </interface>

View File

@@ -287,6 +287,7 @@
<object class="GtkButton" id="addRemoveButton"> <object class="GtkButton" id="addRemoveButton">
<property name="label" translatable="yes">Add to Library</property> <property name="label" translatable="yes">Add to Library</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="receives_default">True</property> <property name="receives_default">True</property>
<signal name="clicked" handler="do_add_clicked" swapped="no"/> <signal name="clicked" handler="do_add_clicked" swapped="no"/>

151
cardvault/gui/wants.glade Normal file
View File

@@ -0,0 +1,151 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkListStore" id="wantsListsStore">
<columns>
<!-- column-name name -->
<column type="gchararray"/>
<!-- column-name Displayname -->
<column type="gchararray"/>
</columns>
</object>
<object class="GtkPaned" id="wantsView">
<property name="name">Wants</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<signal name="show" handler="do_reload_wants" swapped="no"/>
<child>
<object class="GtkBox" id="wantsLeftPane">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkScrolledWindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">never</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="wantsListsTree">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="model">wantsListsStore</property>
<property name="search_column">0</property>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
<child>
<object class="GtkTreeViewColumn" id="col_name">
<property name="title" translatable="yes">Wants List</property>
<child>
<object class="GtkCellRendererText" id="cell"/>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkEntry" id="addWantsListEntry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="placeholder_text" translatable="yes">New Wants List</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="addWatnsListButton">
<property name="label" translatable="yes">Add</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="on_new_wants_list_clicked" object="addWantsListEntry" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="resize">False</property>
<property name="shrink">True</property>
</packing>
</child>
<child>
<object class="GtkBox" id="wantsRightPane">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="wantsToolBar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkOverlay" id="wantsListContainer">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="resize">True</property>
<property name="shrink">True</property>
</packing>
</child>
</object>
</interface>

View File

@@ -1,20 +1,23 @@
import gi import gi
gi.require_version('Gtk', '3.0')
import datetime import datetime
import os import os
from gi.repository import Gtk from gi.repository import Gtk
from typing import Type
gi.require_version('Gtk', '3.0')
from cardvault import lib_funct from cardvault import lib_funct
from cardvault import search_funct from cardvault import search_funct
from cardvault import wants_funct
from cardvault import util from cardvault import util
from cardvault import application
class Handlers: class Handlers:
def __init__(self, app): def __init__(self, app):
self.app = Type[application.Application]
self.app = app self.app = app
# ---------------------------------Main Window---------------------------------------------- # ---------------------------------Main Window----------------------------------------------
def do_save_library(self, item): def do_save_library(self, item):
self.app.save_library() self.app.save_library()
@@ -78,7 +81,7 @@ class Handlers:
if response == Gtk.ResponseType.YES: if response == Gtk.ResponseType.YES:
self.app.save_library() self.app.save_library()
# ---------------------------------Search---------------------------------------------- # ---------------------------------Search----------------------------------------------
def do_search_cards(self, sender): def do_search_cards(self, sender):
search_term = self.app.ui.get_object("searchEntry").get_text() search_term = self.app.ui.get_object("searchEntry").get_text()
@@ -110,8 +113,9 @@ class Handlers:
card = card_view.lib[card_id] card = card_view.lib[card_id]
self.app.add_card_to_lib(card) self.app.add_card_to_lib(card)
search_funct.reload_serach_view(self.app) search_funct.reload_serach_view(self.app)
self.app.ui.get_object("searchEntry").grab_focus()
# ---------------------------------Library---------------------------------------------- # ---------------------------------Library----------------------------------------------
def do_reload_library(self, view): def do_reload_library(self, view):
lib_funct.reload_library(self.app) lib_funct.reload_library(self.app)
@@ -222,6 +226,19 @@ class Handlers:
lib_funct.reload_library(self.app, self.app.current_lib_tag) lib_funct.reload_library(self.app, self.app.current_lib_tag)
lib_funct.reload_tag_list(self.app, preserve=True) lib_funct.reload_tag_list(self.app, preserve=True)
# ---------------------------------Wants----------------------------------------------
def do_reload_wants(self, view):
wants_funct.reload_wants_view(self.app)
def on_new_wants_list_clicked(self, entry):
name = entry.get_text()
# Check if list name already exists
if self.app.wants.__contains__(name):
return
self.app.add_want_list(name)
wants_funct.reload_wants_view(self.app)
# Handlers for TreeViews etc. wich have been not added by Glade # Handlers for TreeViews etc. wich have been not added by Glade
# ---------------------------------Search Tree---------------------------------------------- # ---------------------------------Search Tree----------------------------------------------
@@ -263,3 +280,10 @@ class Handlers:
tree_iter = treeview.get_model().get_iter(path[0]) tree_iter = treeview.get_model().get_iter(path[0])
self.app.ui.get_object("libListPopup").emit('show') self.app.ui.get_object("libListPopup").emit('show')
self.app.ui.get_object("libListPopup").popup(None, None, None, None, 0, event.time) self.app.ui.get_object("libListPopup").popup(None, None, None, None, 0, event.time)
return True
# ---------------------------------Wants Tree----------------------------------------------
def on_wants_card_selected(self, tree, row, column):
# TODO
pass

View File

@@ -1,7 +1,7 @@
from cardvault import cardlist from cardvault import cardlist
import gi import gi
from gi.repository import Gtk
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
def init_library_view(app): def init_library_view(app):
@@ -22,7 +22,7 @@ def init_library_view(app):
def reload_library(app, tag=None): def reload_library(app, tag=None):
if tag == "Untagged": if tag == "Untagged" or tag == "All":
lib = app.get_untagged_cards() lib = app.get_untagged_cards()
tag = None tag = None
else: else:

View File

@@ -28,7 +28,9 @@ def init_search_view(app):
def reload_serach_view(app): def reload_serach_view(app):
pass results_tree = app.ui.get_object("searchResults").get_child()
cards = results_tree.lib
results_tree.update(cards, True)
def get_filters(app): def get_filters(app):

View File

@@ -6,13 +6,13 @@ import re
from urllib import request from urllib import request
import gi import gi
gi.require_version('Gtk', '3.0')
from gi.repository import GdkPixbuf
import six.moves.cPickle as pickle import six.moves.cPickle as pickle
from PIL import Image as PImage from PIL import Image as PImage
from gi.repository import GdkPixbuf
gi.require_version('Gtk', '3.0')
from mtgsdk import Set from mtgsdk import Set
from mtgsdk import Card
from mtgsdk import MtgException from mtgsdk import MtgException
# Title of the Program Window # Title of the Program Window
@@ -72,13 +72,13 @@ class LogLevel(enum.Enum):
Info = 3 Info = 3
def log(message, log_level): def log(message: str, log_level: LogLevel):
if log_level.value <= LOG_LEVEL: if log_level.value <= LOG_LEVEL:
level_string = "[" + log_level.name + "] " level_string = "[" + log_level.name + "] "
print(level_string + message) print(level_string + message)
def parse_config(filename, default): def parse_config(filename: str, default: dict):
config = copy.copy(default) config = copy.copy(default)
try: try:
with open(filename) as configfile: with open(filename) as configfile:
@@ -99,24 +99,25 @@ def parse_config(filename, default):
return config return config
def save_config(config_dict, filename): def save_config(config: dict, filename: str):
path = os.path.dirname(filename) path = os.path.dirname(filename)
if not os.path.isdir(path): if not os.path.isdir(path):
os.mkdir(path) os.mkdir(path)
with open(filename, 'wb') as configfile: with open(filename, 'wb') as configfile:
configfile.write(json.dumps(config_dict, sort_keys=True, configfile.write(json.dumps(config, sort_keys=True,
indent=4, separators=(',', ': ')).encode('utf-8')) indent=4, separators=(',', ': ')).encode('utf-8'))
def get_root_filename(filename): def get_root_filename(filename: str) -> str:
return os.path.expanduser(os.path.join('~', '.cardvault', filename)) return os.path.expanduser(os.path.join('~', '.cardvault', filename))
def get_ui_filename(filename):
def get_ui_filename(filename: str) -> str:
return os.path.expanduser(os.path.join(os.path.dirname(__file__), 'gui', filename)) return os.path.expanduser(os.path.join(os.path.dirname(__file__), 'gui', filename))
def reload_image_cache(path): def reload_image_cache(path: str) -> dict:
cache = {} cache = {}
if not os.path.isdir(path): if not os.path.isdir(path):
os.mkdir(path) os.mkdir(path)
@@ -132,7 +133,7 @@ def reload_image_cache(path):
return cache return cache
def reload_preconstructed_icons(path): def reload_preconstructed_icons(path: str) -> dict:
cache = {} cache = {}
if not os.path.exists(path): if not os.path.exists(path):
os.makedirs(path) os.makedirs(path)
@@ -156,10 +157,10 @@ def reload_preconstructed_icons(path):
return cache return cache
def load_mana_icons(path): def load_mana_icons(path: str) -> dict:
if not os.path.exists(path): if not os.path.exists(path):
log("Directory for mana icons not found " + path, LogLevel.Error) log("Directory for mana icons not found " + path, LogLevel.Error)
return return {}
icons = {} icons = {}
filenames = os.listdir(path) filenames = os.listdir(path)
for file in filenames: for file in filenames:
@@ -170,14 +171,14 @@ def load_mana_icons(path):
return icons return icons
def load_sets(filename): def load_sets(filename: str) -> dict:
if not os.path.isfile(filename): if not os.path.isfile(filename):
# use mtgsdk api to retrieve al list of all sets # use mtgsdk api to retrieve al list of all sets
try: try:
sets = Set.all() sets = Set.all()
except MtgException as err: except MtgException as err:
log(str(err), LogLevel.Error) log(str(err), LogLevel.Error)
return return {}
# Serialize the loaded data to a file # Serialize the loaded data to a file
pickle.dump(sets, open(filename, 'wb')) pickle.dump(sets, open(filename, 'wb'))
# Deserialize set data from local file # Deserialize set data from local file
@@ -197,7 +198,7 @@ def export_library(path, file):
log(str(err), LogLevel.Error) log(str(err), LogLevel.Error)
def import_library(path): def import_library(path: str) -> ():
try: try:
imported = pickle.load(open(path, 'rb')) imported = pickle.load(open(path, 'rb'))
except pickle.UnpicklingError as err: except pickle.UnpicklingError as err:
@@ -213,7 +214,7 @@ def import_library(path):
tags = {} tags = {}
log("Library imported", LogLevel.Info) log("Library imported", LogLevel.Info)
return (library, tags) return library, tags
def save_file(path, file): def save_file(path, file):
@@ -226,7 +227,7 @@ def save_file(path, file):
log("Saved file " + path, LogLevel.Info) log("Saved file " + path, LogLevel.Info)
def load_file(path): def load_file(path: str):
if not os.path.isfile(path): if not os.path.isfile(path):
log(path + " does not exist", LogLevel.Warning) log(path + " does not exist", LogLevel.Warning)
return return
@@ -238,22 +239,22 @@ def load_file(path):
return loaded return loaded
def load_dummy_image(sizex, sizey): def load_dummy_image(size_x: int, size_y: int) -> GdkPixbuf:
return GdkPixbuf.Pixbuf.new_from_file_at_size(os.path.dirname(__file__) return GdkPixbuf.Pixbuf.new_from_file_at_size(os.path.dirname(__file__)
+ '/resources/images/dummy.jpg', sizex, sizey) + '/resources/images/dummy.jpg', size_x, size_y)
def load_card_image_online(card, sizex, sizey): def load_card_image_online(card, size_x: int, size_y: int) -> GdkPixbuf:
url = card.image_url url = card.image_url
if url is None: if url is None:
log("No Image URL for " + card.name, LogLevel.Warning) log("No Image URL for " + card.name, LogLevel.Warning)
return load_dummy_image(sizex, sizey) return load_dummy_image(size_x, size_y)
filename = IMAGE_CACHE_PATH + str(card.multiverse_id) + ".png" filename = IMAGE_CACHE_PATH + str(card.multiverse_id) + ".png"
request.urlretrieve(url, filename) request.urlretrieve(url, filename)
return GdkPixbuf.Pixbuf.new_from_file_at_size(filename, sizex, sizey) return GdkPixbuf.Pixbuf.new_from_file_at_size(filename, size_x, size_y)
def create_mana_icons(icon_dict, mana_string): def create_mana_icons(icons: dict, mana_string: str) -> GdkPixbuf:
# Convert the string to a List # Convert the string to a List
safe_string = mana_string.replace("/", "-") safe_string = mana_string.replace("/", "-")
list = re.findall("{(.*?)}", safe_string) list = re.findall("{(.*?)}", safe_string)
@@ -262,13 +263,14 @@ def create_mana_icons(icon_dict, mana_string):
# Compute horizontal size for the final image # Compute horizontal size for the final image
imagesize = len(list) * 105 imagesize = len(list) * 105
image = PImage.new("RGBA", (imagesize, 105)) image = PImage.new("RGBA", (imagesize, 105))
# incerment for each position of an icon (Workaround: 2 or more of the same icon will be rendered in the same poisition) # Increment for each position of an icon
# (Workaround: 2 or more of the same icon will be rendered in the same position)
poscounter = 0 poscounter = 0
# Go through all entries an add the correspondent icon to the final image # Go through all entries an add the correspondent icon to the final image
for icon in list: for icon in list:
xpos = poscounter * 105 xpos = poscounter * 105
try: try:
loaded = icon_dict[icon] loaded = icons[icon]
except KeyError as err: except KeyError as err:
log("No icon file named '" + icon + "' found.", LogLevel.Warning) log("No icon file named '" + icon + "' found.", LogLevel.Warning)
return return

28
cardvault/wants_funct.py Normal file
View File

@@ -0,0 +1,28 @@
from cardvault import cardlist
from cardvault import application
def init_wants_view(app: 'application.Application'):
# Get container for Cardlist Tree
container = app.ui.get_object("wantsListContainer")
# Create new Cardlist
card_list = cardlist.CardList(True, app)
card_list.set_name("wantsScroller")
# Show details
card_list.list.connect("row-activated", app.handlers.on_wants_card_selected)
# Add card list to container
container.add(card_list)
container.add_overlay(app.ui.get_object("wantsOverlay"))
container.show_all()
# Hide no results overlay
app.ui.get_object("wantsOverlay").set_visible(False)
def reload_wants_view(app: 'application.Application'):
store = app.ui.get_object("wantsListsStore")
store.clear()
for list_name in app.wants.keys():
display_name = list_name + " (" + str(len(app.wants[list_name])) + ")"
store.append([list_name, display_name])

View File

@@ -37,5 +37,5 @@ setup(
'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.5',
'License :: OSI Approved :: MIT License', 'License :: OSI Approved :: MIT License',
] ],
) )