PK7fFD^33infocards/archive.py# -*- coding: utf-8 -*- # # Simple information card archive library # https://github.com/rmed/infocards # # Copyright (C) 2015 Rafael Medina García # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from __future__ import absolute_import from datetime import datetime from fuzzywuzzy import fuzz from peewee import * from .exceptions import ArchiveConfigException, ArchiveConnectionException from .exceptions import ArchiveIntegrityException, ArchiveOperationException from .models import _db_proxy, Card, CardObj, Section, SectionObj, Relation class Archive(object): def __init__(self, **kwargs): """ Initialize the archive according to the parameters passed. These parameters vary from one DBMS to another. """ # Need to initialize the database for the models self.db = self._init_db(**kwargs) _db_proxy.initialize(self.db) try: # Create tables self.db.create_tables([Card, Section, Relation], True) except ImproperlyConfigured as e: raise ArchiveConfigException(str(e)) def _init_db(self, **kwargs): """ Parse the arguments and initialize the proper database. The parameters used for each database are given directly to their specific connectors: - MySQL: PyMySQL - SQLite: sqlite3 - PostgreSQL: psycopg2 Check their documentation for more information on the available parameters. Here we only care about the database type, which could be either 'mysql', 'sqlite' or 'postgres' Returns an already initialized databased. """ db_name = kwargs.pop('db_name') db_type = kwargs.pop('db_type') try: if db_type == 'mysql': return MySQLDatabase(db_name, **kwargs) elif db_type == 'postgres': return PostgresqlDatabase(db_name, **kwargs) elif db_type == 'sqlite': return SqliteDatabase(db_name) except OperationalError as e: raise ArchiveConnectionException(str(e)) raise ArchiveConnectionException('Invalid database type') def add_card_to_section(self, cid=0, ctitle="", sid=0, sname=""): """ Create a card-section relation. cid -- card id to add ctitle -- title of the card add sid -- section id sname -- section name Returns boolean for success or failure """ attrs = { 'card': None, 'section': None } if cid: attrs['card'] = cid else: attrs['card'] = self.get_card(title=ctitle).id if sid: attrs['section'] = sid else: attrs['section'] = self.get_section(name=sname).id try: rel = Relation.create(**attrs) except DoesNotExist as e: return False return True def cards(self): """ Return a generator for all the cards in the archive. """ cards = Card.select() for card in cards: yield CardObj(card) def delete_card(self, cid=0, title=""): """ Delete a card from the archive. This will also delete any dependent relation. Returns boolean for success or failure. """ try: if cid: card = Card.get(Card.id == cid) elif title: card = Card.get(Card.title == title) else: return False except DoesNotExist: return False deleted = card.delete_instance(recursive=True, delete_nullable=True) if deleted > 0: return True return False def delete_section(self, name="", sid=0): """ Delete a section from the archive. This will also delete any dependent relation. Returns boolean for success or failure. """ try: if name: section = Section.get(Section.name == name) elif sid: section = Section.get(Section.id == sid) else: return False except DoesNotExist: return False deleted = section.delete_instance(recursive=True, delete_nullable=True) if deleted > 0: return True return False def get_card(self, cid=0, title=""): """ Obtain a specific card from the archive. The card is fetched by its unique id for simplicity, but is also possible to do it by title, given it is also unique. In case both of them are present, card id has higher priority. """ try: if cid: return CardObj(Card.get(Card.id == cid)) elif title: return CardObj(Card.get(Card.title == title)) except DoesNotExist: return None return None def get_section(self, name="", sid=0): """ Obtain a specific section from the archive. In this case, sections should have a simple name that easily identifies them, therefore priority is given to the name rather than to the section id. """ try: if name: return SectionObj(Section.get(Section.name == name)) elif sid: return SectionObj(Section.get(Section.id == sid)) except DoesNotExist: return None return None def modify_card(self, card=None, cid=0, ctitle="", title="", desc="", content="", tags="", author="UNKNOWN"): """ Modifies an already existing card overwriting its information with the one provided. card -- CardObj instance from which to obtain the new information. It is given priority if present. Will also obtain the card id from here. cid -- id of the card to modify ctitle -- title of the card to modify title -- new title for the card desc -- new description for the card content -- new content for the card tags -- new tags for the card author -- author of the modification, defaults to 'UNKNOWN'. This one is not obtained from the card object Note that if the card argument is not provided, only the arguments present will be overwritten, meaning that it is only necessary to pass the relevant information to modify. Returns the updated card """ if card: cid = card.id title = card.title desc = card.desc content = card.content tags = card.tags try: if cid: modcard = Card.get(Card.id == cid) elif title: modcard = Card.get(Card.title == title) except DoesNotExist: return None modcard.title = title if title else modcard.title modcard.desc = desc if desc else modcard.desc modcard.content = content if content else modcard.content modcard.tags = tags if tags else modcard.tags modcard.modified = datetime.now() modcard.modified_by = author try: modcard.save() except IntegrityError as e: self.db.rollback() raise ArchiveIntegrityException(str(e)) return CardObj(modcard) def new_card(self, title, desc, content, tags, author="UNKNOWN"): """ Add a new card to the archive. title -- title of the card, must be unique desc -- short description of the card content -- main content of the card tags -- space-separated tags for the card author -- optional name of the author of the card Returns the newly created card """ attrs = { 'title': title, 'desc': desc, 'content': content, 'tags': tags, 'modified': datetime.now(), 'modified_by': author } try: return CardObj(Card.create(**attrs)) except IntegrityError as e: self.db.rollback() raise ArchiveIntegrityException(str(e)) def new_section(self, name): """ Create a new section in the archive. name -- name of the section, must be unique Returns the newly created section """ attrs = { 'name': name } try: return SectionObj(Section.create(**attrs)) except IntegrityError as e: self.db.rollback() raise ArchiveIntegrityException(str(e)) def remove_card_from_section(self, cid=0, ctitle="", sid=0, sname=""): """ Remove a card-section relation. cid -- card id to add ctitle -- title of the card add sid -- section id sname -- section name Returns boolean for success or failure """ if cid: card = cid else: card = self.get_card(title=ctitle).id if sid: section = sid else: section = self.get_section(name=sname).id try: rel = Relation.get( Relation.card == card, Relation.section == section) except DoesNotExist as e: return False deleted = rel.delete_instance() if deleted > 0: return True return False def rename_section(self, newname, oldname="", sid=0): """ Rename a section. Returns the new section """ try: if oldname: section = Section.get(Section.name == name) elif sid: section = Section.get(Section.id == sid) else: return None except DoesNotExist: raise ArchiveOperationException('section does not exist') section.name = newname try: section.save() except IntegrityError as e: self.db.rollback() raise ArchiveIntegrityException(str(e)) return SectionObj(section) def search(self, query, sname="", sid=0, likelihood=80, relevance=50): """ Search for relevant cards in the archive. query -- search terms, separated by blankspace section -- section to perform the search in. If not provided, will search the whole archive. likelihood -- percentage for which to words should be considered similar. relevance -- percentage of query terms that must be present in a card for it to be considered relevant Returns a generator. """ search_terms = set([t.lower() for t in query.split()]) if not search_terms: return [] # Get the list of cards to iterate if section_id or section_name: section = self.get_section(section_name, section_id) if not section: return [] # Raw card search cards = (Card .select() .join(Relation) .join(Section) .where(Section.id == section.id)) else: cards = Card.select() # Compare each card for card in cards: s_card = "%s %s %s %s" % ( str(card.id), card.title, card.desc, card.tags) card_terms = set([t.lower() for t in s_card.split()]) common = set() for c_term in card_terms: # Check each search term with those from card for s_term in search_terms: if fuzz.partial_ratio(s_term, c_term) >= 80: common.add(s_term) break # Check if the card is relevant if int((len(common) / len(search_terms)) * 100) < 50: continue yield CardObj(card) def sections(self): """ Return a generator for all the sections in the archive. """ sections = Section.select() for section in sections: yield SectionObj(section) PKnE;S_L L infocards/card.py# -*- coding: utf-8 -*- # # Simple information card archive library # https://github.com/RMed/infocards # # Copyright (C) 2014 Rafael Medina García # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . """ .. module:: card :platform: Unix, Windows :synopsis: Card operations .. moduleauthor:: Rafael Medina García """ from __future__ import absolute_import from .exceptions import ParamError class Card(object): """ Cards have a simple structure and contain a small amount of information stored as plain text in the *Archive*. :param str title: title of the card :param str description: a small description of the card :param str content: content of the card :param str tags: tags are stored as a sequence of words and/or sentences separated by whitespaces in the archive. However, the *Card* object will store the tags as a list for easier access. :param datetime modified: last modification's date and time """ def __init__(self, title, description, content, tags, modified): self.title = title self.description = description self.content = content self.tags = self.tag_list(tags) self.modified = modified @staticmethod def normalize(tag_list=None, tag_string=None): """ Normalize a tag string or list. Normalization is achieved by removing repeated words and transforming every word into lowercase. :param list tag_list: tag list to normalize (useful when modifying card tags) :param str tag_string: tag string to normalize """ if tag_list and tag_string: raise ParamError("Only one parameter type may be normalized") elif tag_list: return sorted(set([t.lower() for t in tag_list])) elif tag_string: return ' '.join(sorted(set( [t.lower() for t in tag_string.split()]))) else: # Nothing to normalize return @staticmethod def tag_list(tag_string): """ Obtain a tag list from a string. This method is called when a new card is created. It will remove duplicate tags, set the tags to lowercase and sort the list when created. :param str tag_string: string of tags separated by whitespaces """ return Card.normalize(tag_list=tag_string.split()) @staticmethod def tag_string(tag_list): """ Obtain a string of tags from a list. Each tag will be separated by a whitespace. :param list tag_list: list of tags to convert """ return Card.normalize(tag_string=' '.join(tag_list)) PK7fFXJ((infocards/exceptions.py# -*- coding: utf-8 -*- # # Simple information card archive library # https://github.com/rmed/infocards # # Copyright (C) 2015 Rafael Medina García # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # This module simply contains simple exceptions for use in the archive class ArchiveException(Exception): def __init__(self, msg): self.message = msg def __str__(self): return self.message class ArchiveConfigException(ArchiveException): pass class ArchiveConnectionException(ArchiveException): pass class ArchiveIntegrityException(ArchiveException): pass class ArchiveOperationException(ArchiveException): pass PK7fFF infocards/models.py# -*- coding: utf-8 -*- # # Simple information card archive library # https://github.com/rmed/infocards # # Copyright (C) 2015 Rafael Medina García # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from peewee import * # Database information can only be known at run-time _db_proxy = Proxy() class BaseModel(Model): class Meta: database = _db_proxy class Card(BaseModel): title = CharField(unique=True) desc = CharField() content = TextField() tags = CharField() modified = DateTimeField() modified_by = CharField() class CardObj(object): def __init__(self, card): """ Create a dummy card object in order to prevent modifications in the database from outside the archive. """ self.id = card.id self.title = card.title self.desc = card.desc self.content = card.content self.tags = card.tags self.modified = card.modified self.modified_by = card.modified_by def sections(self): """ Get all the the sections this card appears in. """ sections = (Section .select() .join(Relation) .join(Card) .where(Card.id == self.id)) for section in sections: yield SectionObj(section) class Section(BaseModel): name = CharField(unique=True) class SectionObj(object): def __init__(self, section): """ Create a dummy section object in order to prevent modifications in the database from outside the the archive. """ self.id = section.id self.name = section.name def cards(self): """ Get all the cards in the section. """ cards = (Card .select() .join(Relation) .join(Section) .where(Section.id == self.id)) for card in cards: yield CardObj(card) class Relation(BaseModel): section = ForeignKeyField(Section) card = ForeignKeyField(Card) class Meta: primary_key = CompositeKey('section', 'card') PK7fFinfocards/_version.py# -*- coding: utf-8 -*- # # Simple information card archive library # https://github.com/rmed/infocards # # Copyright (C) 2015 Rafael Medina García # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. __version__ = '0.5.0' PK7fFinfocards/__init__.pyPKgFD-h)infocards-0.5.0.dist-info/DESCRIPTION.rstinfocards ========= A small Python library for managing **information cards** in an archive. Supports MySQL, PostgreSQL and SQLite databases for the backend. **Compatible with Python 2 and 3.** License: **GPLv2** **Note: from version 0.5.0 and onwards, infocards is not backwards compatible.** Requirements ============ Basic requirements (installed automatically with ``pip``) are: - `fuzzywuzzy `__ 0.5.0 - `peewee `__ 2.6.3 - `python-Levenshtein `__ 0.12.0 If you want to use MySQL or PostgreSQL for the backend, you will also need: - `psycopg2 `__ 2.6.1 - `PyMySQL `__ 0.6.6 Installation ============ Installing from source: :: $ setup.py install Installing from the Package Index (recommended): :: $ pip install infocards Documentation ============= Documentation is `available online `__ and contains the API reference as well as examples. You may also build the documentation source in the ``docs/`` directory using MKDocs. PKgF>!'infocards-0.5.0.dist-info/metadata.json{"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Topic :: Database", "Topic :: Utilities", "License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4"], "extensions": {"python.details": {"contacts": [{"email": "rafamedgar@gmail.com", "name": "Rafael Medina Garc\u00eda", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/rmed/infocards"}}}, "extras": [], "generator": "bdist_wheel (0.24.0)", "keywords": ["information", "info", "card", "archive", "database"], "license": "GPLv2+", "metadata_version": "2.0", "name": "infocards", "run_requires": [{"requires": ["python-Levenshtein (==0.12.0)", "fuzzywuzzy (==0.5.0)", "peewee (==2.6.3)"]}], "summary": "Simple information card archive library", "version": "0.5.0"}PKgF //"infocards-0.5.0.dist-info/pbr.json{"git_version": "ed8e451", "is_release": false}PKgFcq 'infocards-0.5.0.dist-info/top_level.txtinfocards PKgF3onninfocards-0.5.0.dist-info/WHEELWheel-Version: 1.0 Generator: bdist_wheel (0.24.0) Root-Is-Purelib: true Tag: py2-none-any Tag: py3-none-any PKgF1"infocards-0.5.0.dist-info/METADATAMetadata-Version: 2.0 Name: infocards Version: 0.5.0 Summary: Simple information card archive library Home-page: https://github.com/rmed/infocards Author: Rafael Medina García Author-email: rafamedgar@gmail.com License: GPLv2+ Keywords: information info card archive database Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Topic :: Database Classifier: Topic :: Utilities Classifier: License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+) Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Requires-Dist: python-Levenshtein (==0.12.0) Requires-Dist: fuzzywuzzy (==0.5.0) Requires-Dist: peewee (==2.6.3) infocards ========= A small Python library for managing **information cards** in an archive. Supports MySQL, PostgreSQL and SQLite databases for the backend. **Compatible with Python 2 and 3.** License: **GPLv2** **Note: from version 0.5.0 and onwards, infocards is not backwards compatible.** Requirements ============ Basic requirements (installed automatically with ``pip``) are: - `fuzzywuzzy `__ 0.5.0 - `peewee `__ 2.6.3 - `python-Levenshtein `__ 0.12.0 If you want to use MySQL or PostgreSQL for the backend, you will also need: - `psycopg2 `__ 2.6.1 - `PyMySQL `__ 0.6.6 Installation ============ Installing from source: :: $ setup.py install Installing from the Package Index (recommended): :: $ pip install infocards Documentation ============= Documentation is `available online `__ and contains the API reference as well as examples. You may also build the documentation source in the ``docs/`` directory using MKDocs. PKgF&M'' infocards-0.5.0.dist-info/RECORDinfocards/archive.py,sha256=B2w_ycwQD3dHdZ_8gBJxa67eBP0S_LTSXA6uBA8Ov-s,13216 infocards/card.py,sha256=_1o1t6H_6oVtv8bbbEn3ycr3OBUbvh8Gs73Q1PqomyY,3404 infocards/exceptions.py,sha256=xGVup16JV6yOBoP3gjemnDxwpmv9MLRmqz57tXSAUaI,1320 infocards/models.py,sha256=9_zMcnve02CBA60cpUzMLuBbicgIYeiDjDoxP92LTHc,2774 infocards/_version.py,sha256=EqFhsGRAlAYYbWc2ky-mqa9_xDnwPM7ZqBlvYbbKWuk,902 infocards/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 infocards-0.5.0.dist-info/DESCRIPTION.rst,sha256=HAsGQDgrNFo_VUHQdf91Y7YsiYy5bysSLAzF19I_biE,1187 infocards-0.5.0.dist-info/METADATA,sha256=n6Yhqr2eSGhZQ45FL5uHLyA-FfaINcr_Jp0i832kpXM,2040 infocards-0.5.0.dist-info/metadata.json,sha256=Gfg5iqI-sJHtLntdJfzvMwEp6jaqpDZCnD-YeGL9npc,1010 infocards-0.5.0.dist-info/pbr.json,sha256=4aELlMoluXl79xaKKuYudfQx07Oh4FQJrlBnlplnjm8,47 infocards-0.5.0.dist-info/RECORD,, infocards-0.5.0.dist-info/top_level.txt,sha256=4OvBvucCdwoqkL18HevIRx3d_sV89_fHMT8xKwoGwjo,10 infocards-0.5.0.dist-info/WHEEL,sha256=AvR0WeTpDaxT645bl5FQxUK6NPsTls2ttpcGJg3j1Xg,110 PK7fFD^33infocards/archive.pyPKnE;S_L L 3infocards/card.pyPK7fFXJ((MAinfocards/exceptions.pyPK7fFF Finfocards/models.pyPK7fFQinfocards/_version.pyPK7fFjUinfocards/__init__.pyPKgFD-h)Uinfocards-0.5.0.dist-info/DESCRIPTION.rstPKgF>!'Zinfocards-0.5.0.dist-info/metadata.jsonPKgF //"^infocards-0.5.0.dist-info/pbr.jsonPKgFcq '-_infocards-0.5.0.dist-info/top_level.txtPKgF3onn|_infocards-0.5.0.dist-info/WHEELPKgF1"'`infocards-0.5.0.dist-info/METADATAPKgF&M'' _hinfocards-0.5.0.dist-info/RECORDPK l