<commit_before># -*- coding: utf-8 -*-
"""
The goal of this code is to support hosting a client library. This module
should in the end function similarly to the Mendeley Desktop.

Features:
---------
1) Initializes a representation of the documents stored in a user's library
2) Synchronizes the local library with updates that have been made remotely
3) Provides an interface

TODO:
-----
1) Need to implement getting new papers and deleting old ones

"""

from datetime import datetime

from timeit import default_timer as ctime
import os
import pandas as pd
import pickle

import pdb

from .utils import float_or_none_to_string as fstr
from .utils import get_list_class_display as cld
from . import utils
from .api import API
from . import models

class UserLibrary:
    """
    Attributes
    ----------
    sync_result :
    doc_objects :
    docs : Pandas entry
    raw : list of json object dicts
    raw_trash : list of dicts

    """

    FILE_VERSION = 1

    def __init__(self, user_name=None, verbose=False):
        self.api = API(user_name=user_name)
        self.user_name = self.api.user_name
        self.verbose = verbose

        # path handling
        # -------------
        root_path = utils.get_save_root(['client_library'], True)
        save_name = utils.user_name_to_file_name(self.user_name) + '.pickle'
        self.file_path = os.path.join(root_path, save_name)

        self._load()

        self.sync()

    def __repr__(self):
        pv = ['api', cld(self.api),
              'user_name', self.user_name,
              'docs', cld(self.docs),
              'raw', cld(self.raw),
              'doc_objects', cld(self.doc_objects)]
        return utils.property_values_to_string(pv)

    def sync(self):
        """
        Syncing approach:

        ? How do we know if something has been restored from the trash?

        """

        sync_result = Sync(self.api, self.raw, verbose=self.verbose)
        self.sync_result = sync_result
        self.raw = sync_result.raw
        self.docs = sync_result.docs
        self._save()

    def get_document(self, doi, return_json=False):
        """
        Returns the document (i.e. metadata) for a given DOI,
        if the DOI is found in the library.

        #TODO: Build in support for other identifier searches

        Parameters
        ----------
        doi : str
        return_json : bool

        Returns
        -------
        models.Document object
            If return_json is False
        JSON
            If return_json is True

        """

        # Search through pandas dataframe for a document with target DOI.
        # Entries are indexed using the document ID, so when the correct
        # DOI is found, the document ID is the index of that entry.
        document = self.docs.loc[self.docs['doi'] == doi]
        doc_id = document.index[0]
        document_json = document['json'][0]

        if doc_id is None:
            raise KeyError("DOI not found in library")

        if return_json:
            return document_json
        else:
            doc_obj = models.Document(document_json, API())
            return doc_obj

    def _load(self):
        # TODO: Check that the file is not empty ...
        if os.path.isfile(self.file_path):
            with open(self.file_path, 'rb') as pickle_file:
                d = pickle.load(pickle_file)

            self.raw = d['raw']
        else:
            self.raw = None
            self.docs = None

    def _save(self):
        d = dict()
        d['file_version'] = self.FILE_VERSION
        d['raw'] = self.raw
        # d['raw_trash'] = self.raw_trash
        with open(self.file_path, 'wb') as pickle_file:
            pickle.dump(d, pickle_file)

class Sync(object):
    """
    This object should perform the syncing and include some
    debugging information as well

    Attributes
    ----------
    time_deleted_check
    time_full_retrieval
    time_modified_check
    time_trash_retrieval
    time_update_sync

    #TODO: Update with other attributes in this class

    """

    def __init__(self, api, raw, verbose=False):
        self.time_full_retrieval = None
        self.time_deleted_check = None
        self.time_trash_retrieval = None
        self.time_modified_check = None
        self.time_modified_processing = None
        self.newest_modified_time = None
        self.n_docs_removed = 0

        self.time_update_sync = None

        self.api = api
        self.verbose = verbose

        self.raw = raw

        #pdb.set_trace()

        # Populated_values
        # -----------------
        self.deleted_ids = None
        self.trash_ids = None
        self.new_and_updated_docs = None

        if self.raw is None:
            self.full_sync()
        else:
            self.update_sync()

    def __repr__(self):
        pv = ['raw', cld(self.raw),
            'docs', cld(self.docs),
            'time_full_retrieval', fstr(self.time_full_retrieval),
            'time_update_sync', fstr(self.time_update_sync),
            'newest_modified_time', self.newest_modified_time,
            'time_deleted_check', fstr(self.time_deleted_check),
            'time_trash_retrieval', fstr(self.time_trash_retrieval),
            'time_modified_check', fstr(self.time_modified_check),
            'time_modified_processing', fstr(self.time_modified_processing),
            'deleted_ids', cld(self.deleted_ids),
            'trash_ids', cld(self.trash_ids),
            'n_docs_removed', '%d' % self.n_docs_removed,
            'new_and_updated_docs', cld(self.new_and_updated_docs)]

        return utils.property_values_to_string(pv)

    def full_sync(self):

        t1 = ctime()
        self.verbose_print('Starting retrieval of all documents')

        # TODO: Change limit to -1, build in support for getting all
        # within the caller
        doc_set = self.api.documents.get(limit=500, view='all')

        self.raw = [x.json for x in doc_set]
        self.docs = _raw_to_data_frame(self.raw)

        self.full_retrieval_time = ctime() - t1

        if self.raw is not None:
            self.verbose_print('Finished retrieving all documents (n=%d) in %s seconds'
                                % (len(self.raw), fstr(self.full_retrieval_time)))
        else:
            self.verbose_print('No documents found in %s seconds'
                               % fstr(self.full_retrieval_time))

    def update_sync(self):

        self.verbose_print('Running "UPDATE SYNC"')

        start_sync_time = ctime()

        #Let's work with everything as a dataframe
        self.docs = _raw_to_data_frame(self.raw)

        self.raw = self.docs['json'].tolist()

        #Determine the document that was updated most recently. We'll ask for
        #everything that changed after that time. This avoids time sync
        #issues with the server and the local computer since everything
        #is done relative to the timestamps from the server.
        newest_modified_time = self.docs['last_modified'].max()
        self.newest_modified_time = newest_modified_time

        #Remove old ids
        #------------------------------------
        self.get_trash_ids()
        self.get_deleted_ids(newest_modified_time)
        self.remove_old_ids()

        #Process new and updated documents
        # ------------------------------------
        updates_and_new_entries_start_time = ctime()
        self.verbose_print('Checking for modified or new documents')
        self.get_updates_and_new_entries(newest_modified_time)
        self.time_modified_processing = ctime() - updates_and_new_entries_start_time
        self.verbose_print('Done updating modified and new documents')

        self.time_update_sync = ctime() - start_sync_time

        self.verbose_print('Done running "UPDATE SYNC" in %s seconds' % fstr(self.time_update_sync))

    def get_updates_and_new_entries(self, newest_modified_time):
        """
        #3) check modified since - add/update as necessary
        #-------------------------------------------------
        #I think for now to keep things simple we'll relate everything
        #to the newest last modified value, rather than worrying about
        #mismatches in time between the client and the server
        """

        start_modified_time = ctime()

        # TODO: Include -1 here ...
        doc_set = self.api.documents.get(modified_since=newest_modified_time, view='all')

        raw_au_docs = [x.json for x in doc_set]
        self.new_and_updated_docs = doc_set.docs
        self.time_modified_check = ctime() - start_modified_time

        if len(raw_au_docs) == 0:
            return

        # TODO: Update name, I thought this was referring to our official copy
        # which is currently self.docs
        df = _raw_to_data_frame(raw_au_docs)

        is_new_mask = df['created'] > newest_modified_time
        new_rows_df = df[is_new_mask]
        updated_rows_df = df[~is_new_mask]
        if len(new_rows_df) > 0:
            self.verbose_print('%d new documents found' % len(new_rows_df))
            self.docs = self.docs.append(new_rows_df)

        if len(updated_rows_df) > 0:
            self.verbose_print('%d updated documents found' % len(updated_rows_df))
            in_old_mask = updated_rows_df.index.isin(self.docs.index)
            if not in_old_mask.all():
                print('Logic error, updated entries are not in the original')
                import pdb
                pdb.set_trace()
                # raise Exception('Logic error, updated entries are not in the original')

            updated_indices = updated_rows_df.index
            self.docs.drop(updated_indices, inplace=True)

            self.docs = pd.concat([self.docs, updated_rows_df])

    def get_trash_ids(self):
        """
        Here we are looking for documents that have been moved to the trash.

        ??? Can we check the trash that's been moved back to the main
        """

        trash_start_time = ctime()
        self.verbose_print('Checking trash')

        trash_set = self.api.trash.get(limit=500, view='all')
        self.trash_ids = [x.doc_id for x in trash_set]

        self.verbose_print('Finished checking trash, %d documents found' % len(self.trash_ids))
        self.time_trash_retrieval = ctime() - trash_start_time

    def get_deleted_ids(self, newest_modified_time):

        # 2) Check deleted
        deletion_start_time = ctime()
        self.verbose_print('Requesting deleted file IDs')

        # This is way way faster :/ than the documents.get() method although
        # it is only documented sparsly.
        # TODO: We could do the string conversion in the api
        self.deleted_ids = self.api.documents.deleted_files(since=newest_modified_time)

        self.verbose_print('Done requesting deleted file IDs, %d found' % len(self.deleted_ids))
        self.time_deleted_check = ctime() - deletion_start_time

    def remove_old_ids(self):
        # Removal of ids
        # --------------
        ids_to_remove = self.trash_ids + self.deleted_ids
        if len(ids_to_remove) > 0:
            delete_mask = self.docs.index.isin(ids_to_remove)
            keep_mask = ~delete_mask
            self.n_docs_removed = sum(delete_mask)
            self.docs = self.docs[keep_mask]

    def verbose_print(self, msg):
        if self.verbose:
            print(msg)

def _raw_to_data_frame(raw, include_json=True):
    """

    """
    # Note that I'm not using the local attribute
    # as we can then use this for updating new information
    df = pd.DataFrame(raw)

    # len(df) == 0 means that no documents were found.
    # Further operations on df would fail.
    if len(df) == 0:
        return df

    df.set_index('id', inplace=True)

    if include_json:
        df['json'] = raw

    # 2010-03-16T16:39:02.000Z
    # https://github.com/closeio/ciso8601
    # t2 = time.clock()
    df['created'] = df['created'].apply(parse_datetime)
    # print(time.clock() - t2)
    # strptime("2008-09-03T20:56:35.450686Z", "%Y-%m-%dT%H:%M:%S.%fZ")

    # t2 = time.clock()
    df['last_modified'] = df['last_modified'].apply(parse_datetime)
    # print(time.clock() - t2)
    df['issn'] = df['identifiers'].apply(parse_issn)
    df['pmid'] = df['identifiers'].apply(parse_pmid)
    df['doi'] = df['identifiers'].apply(parse_doi)

    return df

def parse_datetime(x):
    return datetime.strptime(x, "%Y-%m-%dT%H:%M:%S.%fZ")

# def datetime_to_string(x):
#    return x.strftime("%Y-%m-%dT%H:%M:%S.%fZ")

def parse_issn(x):
    # This value is not necessarily clean
    # e.g 17517214 => 1751-7214???
    try:
        return x.get('issn', '')
    except:
        return ''

def parse_pmid(x):
    try:
        return x.get('pmid', '')
    except:
        return ''

def parse_doi(x):
    try:
        return x.get('doi', '')
    except:
        return ''
<commit_msg>adding a method to check for document<commit_after># -*- coding: utf-8 -*-
"""
The goal of this code is to support hosting a client library. This module
should in the end function similarly to the Mendeley Desktop.

Features:
---------
1) Initializes a representation of the documents stored in a user's library
2) Synchronizes the local library with updates that have been made remotely
3) Provides an interface

TODO:
-----
1) Need to implement getting new papers and deleting old ones

"""

from datetime import datetime

from timeit import default_timer as ctime
import os
import pandas as pd
import pickle

import pdb

from .utils import float_or_none_to_string as fstr
from .utils import get_list_class_display as cld
from . import utils
from .api import API
from . import models

class UserLibrary:
    """
    Attributes
    ----------
    sync_result :
    doc_objects :
    docs : Pandas entry
    raw : list of json object dicts
    raw_trash : list of dicts

    """

    FILE_VERSION = 1

    def __init__(self, user_name=None, verbose=False):
        self.api = API(user_name=user_name)
        self.user_name = self.api.user_name
        self.verbose = verbose

        # path handling
        # -------------
        root_path = utils.get_save_root(['client_library'], True)
        save_name = utils.user_name_to_file_name(self.user_name) + '.pickle'
        self.file_path = os.path.join(root_path, save_name)

        self._load()

        self.sync()

    def __repr__(self):
        pv = ['api', cld(self.api),
              'user_name', self.user_name,
              'docs', cld(self.docs),
              'raw', cld(self.raw),
              'doc_objects', cld(self.doc_objects)]
        return utils.property_values_to_string(pv)

    def sync(self):
        """
        Syncing approach:

        ? How do we know if something has been restored from the trash?

        """

        sync_result = Sync(self.api, self.raw, verbose=self.verbose)
        self.sync_result = sync_result
        self.raw = sync_result.raw
        self.docs = sync_result.docs
        self._save()

    def get_document(self, doi, return_json=False):
        """
        Returns the document (i.e. metadata) for a given DOI,
        if the DOI is found in the library.

        #TODO: Build in support for other identifier searches

        Parameters
        ----------
        doi : str
        return_json : bool

        Returns
        -------
        models.Document object
            If return_json is False
        JSON
            If return_json is True

        """

        # Search through pandas dataframe for a document with target DOI.
        # Entries are indexed using the document ID, so when the correct
        # DOI is found, the document ID is the index of that entry.
        document = self.docs.loc[self.docs['doi'] == doi]

        if len(document) == 0:
            raise KeyError("DOI not found in library")

        doc_id = document.index[0]
        document_json = document['json'][0]

        if return_json:
            return document_json
        else:
            doc_obj = models.Document(document_json, API())
            return doc_obj

    def check_for_document(self, doi):
        """
        Attempts to call self.get_document. If it runs without
        error, it means the DOI exists in the library, so method returns
        True. If get_document throws a KeyError, it means the DOI wasn't
        found, so method returns False.
        """
        try:
            self.get_document(doi)
            return True
        except KeyError:
            return False

    def _load(self):
        # TODO: Check that the file is not empty ...
        if os.path.isfile(self.file_path):
            with open(self.file_path, 'rb') as pickle_file:
                d = pickle.load(pickle_file)

            self.raw = d['raw']
        else:
            self.raw = None
            self.docs = None

    def _save(self):
        d = dict()
        d['file_version'] = self.FILE_VERSION
        d['raw'] = self.raw
        # d['raw_trash'] = self.raw_trash
        with open(self.file_path, 'wb') as pickle_file:
            pickle.dump(d, pickle_file)

class Sync(object):
    """
    This object should perform the syncing and include some
    debugging information as well

    Attributes
    ----------
    time_deleted_check
    time_full_retrieval
    time_modified_check
    time_trash_retrieval
    time_update_sync

    #TODO: Update with other attributes in this class

    """

    def __init__(self, api, raw, verbose=False):
        self.time_full_retrieval = None
        self.time_deleted_check = None
        self.time_trash_retrieval = None
        self.time_modified_check = None
        self.time_modified_processing = None
        self.newest_modified_time = None
        self.n_docs_removed = 0

        self.time_update_sync = None

        self.api = api
        self.verbose = verbose

        self.raw = raw

        #pdb.set_trace()

        # Populated_values
        # -----------------
        self.deleted_ids = None
        self.trash_ids = None
        self.new_and_updated_docs = None

        if self.raw is None:
            self.full_sync()
        else:
            self.update_sync()

    def __repr__(self):
        pv = ['raw', cld(self.raw),
            'docs', cld(self.docs),
            'time_full_retrieval', fstr(self.time_full_retrieval),
            'time_update_sync', fstr(self.time_update_sync),
            'newest_modified_time', self.newest_modified_time,
            'time_deleted_check', fstr(self.time_deleted_check),
            'time_trash_retrieval', fstr(self.time_trash_retrieval),
            'time_modified_check', fstr(self.time_modified_check),
            'time_modified_processing', fstr(self.time_modified_processing),
            'deleted_ids', cld(self.deleted_ids),
            'trash_ids', cld(self.trash_ids),
            'n_docs_removed', '%d' % self.n_docs_removed,
            'new_and_updated_docs', cld(self.new_and_updated_docs)]

        return utils.property_values_to_string(pv)

    def full_sync(self):

        t1 = ctime()
        self.verbose_print('Starting retrieval of all documents')

        # TODO: Change limit to -1, build in support for getting all
        # within the caller
        doc_set = self.api.documents.get(limit=500, view='all')

        self.raw = [x.json for x in doc_set]
        self.docs = _raw_to_data_frame(self.raw)

        self.full_retrieval_time = ctime() - t1

        if self.raw is not None:
            self.verbose_print('Finished retrieving all documents (n=%d) in %s seconds'
                                % (len(self.raw), fstr(self.full_retrieval_time)))
        else:
            self.verbose_print('No documents found in %s seconds'
                               % fstr(self.full_retrieval_time))

    def update_sync(self):

        self.verbose_print('Running "UPDATE SYNC"')

        start_sync_time = ctime()

        #Let's work with everything as a dataframe
        self.docs = _raw_to_data_frame(self.raw)

        self.raw = self.docs['json'].tolist()

        #Determine the document that was updated most recently. We'll ask for
        #everything that changed after that time. This avoids time sync
        #issues with the server and the local computer since everything
        #is done relative to the timestamps from the server.
        newest_modified_time = self.docs['last_modified'].max()
        self.newest_modified_time = newest_modified_time

        #Remove old ids
        #------------------------------------
        self.get_trash_ids()
        self.get_deleted_ids(newest_modified_time)
        self.remove_old_ids()

        #Process new and updated documents
        # ------------------------------------
        updates_and_new_entries_start_time = ctime()
        self.verbose_print('Checking for modified or new documents')
        self.get_updates_and_new_entries(newest_modified_time)
        self.time_modified_processing = ctime() - updates_and_new_entries_start_time
        self.verbose_print('Done updating modified and new documents')

        self.time_update_sync = ctime() - start_sync_time

        self.verbose_print('Done running "UPDATE SYNC" in %s seconds' % fstr(self.time_update_sync))

    def get_updates_and_new_entries(self, newest_modified_time):
        """
        #3) check modified since - add/update as necessary
        #-------------------------------------------------
        #I think for now to keep things simple we'll relate everything
        #to the newest last modified value, rather than worrying about
        #mismatches in time between the client and the server
        """

        start_modified_time = ctime()

        # TODO: Include -1 here ...
        doc_set = self.api.documents.get(modified_since=newest_modified_time, view='all')

        raw_au_docs = [x.json for x in doc_set]
        self.new_and_updated_docs = doc_set.docs
        self.time_modified_check = ctime() - start_modified_time

        if len(raw_au_docs) == 0:
            return

        # TODO: Update name, I thought this was referring to our official copy
        # which is currently self.docs
        df = _raw_to_data_frame(raw_au_docs)

        is_new_mask = df['created'] > newest_modified_time
        new_rows_df = df[is_new_mask]
        updated_rows_df = df[~is_new_mask]
        if len(new_rows_df) > 0:
            self.verbose_print('%d new documents found' % len(new_rows_df))
            self.docs = self.docs.append(new_rows_df)

        if len(updated_rows_df) > 0:
            self.verbose_print('%d updated documents found' % len(updated_rows_df))
            in_old_mask = updated_rows_df.index.isin(self.docs.index)
            if not in_old_mask.all():
                print('Logic error, updated entries are not in the original')
                import pdb
                pdb.set_trace()
                # raise Exception('Logic error, updated entries are not in the original')

            updated_indices = updated_rows_df.index
            self.docs.drop(updated_indices, inplace=True)

            self.docs = pd.concat([self.docs, updated_rows_df])

    def get_trash_ids(self):
        """
        Here we are looking for documents that have been moved to the trash.

        ??? Can we check the trash that's been moved back to the main
        """

        trash_start_time = ctime()
        self.verbose_print('Checking trash')

        trash_set = self.api.trash.get(limit=500, view='all')
        self.trash_ids = [x.doc_id for x in trash_set]

        self.verbose_print('Finished checking trash, %d documents found' % len(self.trash_ids))
        self.time_trash_retrieval = ctime() - trash_start_time

    def get_deleted_ids(self, newest_modified_time):

        # 2) Check deleted
        deletion_start_time = ctime()
        self.verbose_print('Requesting deleted file IDs')

        # This is way way faster :/ than the documents.get() method although
        # it is only documented sparsly.
        # TODO: We could do the string conversion in the api
        self.deleted_ids = self.api.documents.deleted_files(since=newest_modified_time)

        self.verbose_print('Done requesting deleted file IDs, %d found' % len(self.deleted_ids))
        self.time_deleted_check = ctime() - deletion_start_time

    def remove_old_ids(self):
        # Removal of ids
        # --------------
        ids_to_remove = self.trash_ids + self.deleted_ids
        if len(ids_to_remove) > 0:
            delete_mask = self.docs.index.isin(ids_to_remove)
            keep_mask = ~delete_mask
            self.n_docs_removed = sum(delete_mask)
            self.docs = self.docs[keep_mask]

    def verbose_print(self, msg):
        if self.verbose:
            print(msg)

def _raw_to_data_frame(raw, include_json=True):
    """

    """
    # Note that I'm not using the local attribute
    # as we can then use this for updating new information
    df = pd.DataFrame(raw)

    # len(df) == 0 means that no documents were found.
    # Further operations on df would fail.
    if len(df) == 0:
        return df

    df.set_index('id', inplace=True)

    if include_json:
        df['json'] = raw

    # 2010-03-16T16:39:02.000Z
    # https://github.com/closeio/ciso8601
    # t2 = time.clock()
    df['created'] = df['created'].apply(parse_datetime)
    # print(time.clock() - t2)
    # strptime("2008-09-03T20:56:35.450686Z", "%Y-%m-%dT%H:%M:%S.%fZ")

    # t2 = time.clock()
    df['last_modified'] = df['last_modified'].apply(parse_datetime)
    # print(time.clock() - t2)
    df['issn'] = df['identifiers'].apply(parse_issn)
    df['pmid'] = df['identifiers'].apply(parse_pmid)
    df['doi'] = df['identifiers'].apply(parse_doi)

    return df

def parse_datetime(x):
    return datetime.strptime(x, "%Y-%m-%dT%H:%M:%S.%fZ")

# def datetime_to_string(x):
#    return x.strftime("%Y-%m-%dT%H:%M:%S.%fZ")

def parse_issn(x):
    # This value is not necessarily clean
    # e.g 17517214 => 1751-7214???
    try:
        return x.get('issn', '')
    except:
        return ''

def parse_pmid(x):
    try:
        return x.get('pmid', '')
    except:
        return ''

def parse_doi(x):
    try:
        return x.get('doi', '')
    except:
        return ''
<|endoftext|>


<commit_before>from typing import Type, TypeVar, Mapping, Callable, Union, Collection, Any, Generator, Tuple, Optional

T = TypeVar("T")

class SearchError(TypeError):
    pass

def searchable(search_key_types: Mapping[Type, Union[str, Collection[str]]]) -> Callable[[T], T]:
    search_key_types = dict(search_key_types)

    # For each key type, we want to store the ordered attributes to query.
    # We accept attribute.sub_attribute, so split all target attributes on "."
    for key, types in search_key_types.items():
        if isinstance(types, str):
            types = [types]
        types = [type.split(".") for type in types]
        search_key_types[key] = types

    def search(instance: T, item: Any) -> bool:
        try:
            search_keys = search_key_types[type(item)]
        except KeyError:
            raise SearchError("Attempted to search for invalid type! Accepted types are {types}".format(types=[key_type.__name__ for key_type in search_key_types.keys()]))

        for search_key in search_keys:
            failed = False
            value = instance
            for sub_attribute in search_key:
                try:
                    value = getattr(value, sub_attribute)
                except AttributeError:
                    failed = True
                    break

            # This search key didn't exist for the item
            if failed:
                continue

            # Found the search item directly as an attribute
            if value == item:
                return True

            # Try to pass it along to the attribute's __contains__
            try:
                if item in value:
                    return True
            except (TypeError, SearchError):
                # The attribute doesn't define __contains__ or is searchable and doesn't accept this key type
                continue

    def decorator(cls: T) -> T:
        if hasattr(cls, "__contains__"):
            contains = cls.__contains__

            def new_contains(self, item: Any) -> bool:
                result = contains(self, item)

                # If it's contained by normal means, short circuit
                if result:
                    return True
                try:
                    # Try a search
                    if search(self, item):
                        return True
                except SearchError:
                    # Search doesn't accept that type
                    pass

                # Search and normal __contains__ implementations both were False
                return False
        else:
            new_contains = search

        cls.__contains__ = new_contains

        if cls.__doc__ is not None:
            cls.__doc__ += "\n\n"
        else:
            cls.__doc__ = ""
        cls.__doc__ += "Searchable by {types}".format(types=[key_type.__name__ for key_type in search_key_types.keys()])

        return cls

    return decorator

class SearchableList(list):
    def filter(self, function):
        return filter(function, self)

    def __getitem__(self, item: Any) -> Any:
        try:
            return super().__getitem__(item)
        except (IndexError, TypeError):
            return self.find(item)

    def __contains__(self, item: Any) -> bool:
        return self.contains(item)

    def __delitem__(self, item: Any) -> None:
        try:
            super().__delitem__(item)
        except (IndexError, TypeError):
            self.delete(item)

    def _search_generator(self, item: Any, reverse: bool = False) -> Generator[Any, None, None]:
        """A helper method for `self.search` that returns a generator rather than a list."""
        results = 0
        for _, x in self.enumerate(item, reverse=reverse):
            yield x
            results += 1
        if results == 0:
            raise SearchError(str(item))

    def search(self, item: Any, streaming: bool = False, reverse: bool = False) -> Union["SearchableList", Generator[Any, None, None]]:
        if streaming:
            return self._search_generator(item, reverse=reverse)
        else:
            result = SearchableList(x for _, x in self.enumerate(item, reverse=reverse))
            if len(result) == 0:
                raise SearchError(str(item))
            return result

    def find(self, item: Any, reverse: bool = False) -> Any:
        for _, x in self.enumerate(item, reverse=reverse):
            return x
        raise SearchError(str(item))

    def contains(self, item: Any) -> bool:
        for _, _ in self.enumerate(item):
            return True
        return False

    def enumerate(self, item: Any, reverse: bool = False) -> Generator[Tuple[int, Any], None, None]:
        items = self
        if reverse:
            max = len(items) - 1
            items = reversed(items)
        for index, x in enumerate(items):
            if x == item:
                yield index, x if not reverse else max - index
                continue

            try:
                if item in x:
                    yield index, x if not reverse else max - index
            except TypeError:
                # x doesn't define __contains__
                pass

    def delete(self, item: Any, count: int = float("inf")) -> None:
        deleted = 0
        for index, _ in self.enumerate(item, reverse=True):
            del self[index]
            deleted += 1
            if deleted >= count:
                break
        if deleted == 0:
            raise SearchError(str(item))

class SearchableSet(set):
    def filter(self, function):
        return filter(function, self)

    def __getitem__(self, item: Any) -> Any:
        return self.find(item)

    def __contains__(self, item: Any) -> bool:
        return self.contains(item)

    def __delitem__(self, item: Any) -> None:
        self.delete(item)

    def _search_generator(self, item: Any) -> Generator[Any, None, None]:
        """A helper method for `self.search` that returns a generator rather than a list."""
        results = 0
        for x in self.enumerate(item):
            yield x
            results += 1
        if results == 0:
            raise SearchError(str(item))

    def search(self, item: Any, streaming: bool = False) -> Union["SearchableSet", Generator[Any, None, None]]:
        if streaming:
            return self._search_generator(item)
        else:
            result = SearchableSet(self.enumerate(item))
            if len(result) == 0:
                raise SearchError(str(item))
            return result

    def find(self, item: Any) -> Any:
        for x in self.enumerate(item):
            return x
        raise SearchError(str(item))

    def contains(self, item: Any) -> bool:
        for _ in self.enumerate(item):
            return True
        return False

    def enumerate(self, item: Any) -> Generator[Any, None, None]:
        for x in self:
            if x == item:
                yield x
                continue

            try:
                if item in x:
                    yield x
            except TypeError:
                # x doesn't define __contains__
                pass

    def delete(self, item: Any) -> None:
        to_delete = set(self.enumerate(item))
        if len(to_delete) == 0:
            raise SearchError(str(item))
        for x in to_delete:
            self.remove(x)

class SearchableDictionary(dict):
    def filter(self, function):
        return filter(function, self.items())

    def __getitem__(self, item: Any) -> Any:
        try:
            return super().__getitem__(item)
        except KeyError:
            return self.find(item)

    def __contains__(self, item: Any) -> bool:
        return self.contains(item)

    def __delitem__(self, item: Any) -> None:
        try:
            super().__delitem__(item)
        except KeyError:
            self.delete(item)

    def _search_generator(self, item: Any) -> Generator[Tuple[Any, Any], None, None]:
        """A helper method for `self.search` that returns a generator rather than a list."""
        results = 0
        for key, value in self.enumerate(item):
            yield key, value
            results += 1
        if results == 0:
            raise SearchError(str(item))

    def search(self, item: Any, streaming: bool = False) -> Union["SearchableDictionary", Generator[Tuple[Any, Any], None, None]]:
        if streaming:
            return self._search_generator(item)
        else:
            result = SearchableDictionary(self.enumerate(item))
            if len(result) == 0:
                raise SearchError(str(item))
            return result

    def find(self, item: Any) -> Tuple[Any, Any]:
        for key, value in self.items():
            if key == item:
                return key, value

            try:
                if item in key:
                    return key, value
            except TypeError:
                # key doesn't define __contains__
                pass

            if value == item:
                return key, value

            try:
                if item in value:
                    return key, value
            except TypeError:
                # value doesn't define __contains__
                pass
        raise SearchError(str(item))

    def contains(self, item: Any) -> bool:
        for _, _ in self.enumerate(item):
            return True
        return False

    def enumerate(self, item: Any) -> Generator[Tuple[Any, Any], None, None]:
        for key, value in self.items():
            if key == item:
                yield key, value
                continue

            try:
                if item in key:
                    yield key, value
                    continue
            except TypeError:
                # key doesn't define __contains__
                pass

            if value == item:
                yield key, value
                continue

            try:
                if item in value:
                    yield key, value
                    continue
            except TypeError:
                # value doesn't define __contains__
                pass

    def delete(self, item: Any) -> None:
        to_delete = {key for key, _ in self.enumerate(item)}
        if len(to_delete) == 0:
            raise SearchError(str(item))
        for key in to_delete:
            del self[key]

class SearchableLazyList(SearchableList):
    """A SearchableList where the values of the list are generated on-demand.

    Arguments:
        constructor (callable): A function that returns a generator of the values to be put in the list.
    """
    def __init__(self, generator: Generator):
        self._generator = generator
        self._empty = False
        super().__init__()  # initialize an empty list

    def __str__(self):
        if self._empty:
            return super().__str__()
        else:
            string = super().__str__()
            if string == "[]":
                return "[...]"
            else:
                return string[:-1] + ", ...]"

    def __iter__(self):
        for item in super().__iter__():
            yield item
        while not self._empty:
            yield next(self)

    def __len__(self):
        if self._empty:
            return super().__len__()
        else:
            raise RuntimeError("SearchableLazyList hasn't been fully generated yet and therefore has an unknown length.")

    def __next__(self):
        try:
            value = next(self._generator)
            self.append(value)
        except StopIteration as error:
            self._empty = True
            raise error
        return value

    def _generate_many(self, count: Optional[int] = None):
        if count is not None:
            for _ in range(count):
                next(self)
        else:
            for _ in self:
                pass
            assert self._empty

    def __getitem__(self, item: Any) -> Any:
        try:
            return list.__getitem__(self, item)
        except IndexError:
            # Generate new values until: 1) we get to position `item` (which is an int) or 2) no more values are left
            iterate_until = item - super().__len__() + 1
            try:
                self._generate_many(iterate_until)
            except StopIteration:
                pass
            # Now that we have 1) enough or 2) all the values, try returning again.
            # If we still get an index error, then we have an int that is being searched on.
            try:
                return list.__getitem__(self, item)
            except IndexError:
                return self.find(item)
        except TypeError:
            return self.find(item)

    def __delitem__(self, item: Any) -> None:
        if isinstance(item, int):
            # Make sure we have enough values generated
            try:
                iterate_until = item - super().__len__() + 1
                self._generate_many(iterate_until)
            except StopIteration:
                pass
        try:
            list.__delitem__(self, item)
        except (IndexError, TypeError) as error:
            self.delete(item, count=1)

    def __reversed__(self):
        self._generate_many()
        return super().__reversed__()

    def delete(self, item: Any, count: int = float("inf")) -> None:
        deleted = 0
        for index, _ in self.enumerate(item, reverse=False):
            del self[index]
            deleted += 1
            if deleted >= count:
                break
        if deleted == 0:
            raise SearchError(str(item))
<commit_msg>reverted a few changes __getitem__ and __delitem__ should not work on ints, but .find and .delete should!<commit_after>from typing import Type, TypeVar, Mapping, Callable, Union, Collection, Any, Generator, Tuple, Optional

T = TypeVar("T")

class SearchError(TypeError):
    pass

def searchable(search_key_types: Mapping[Type, Union[str, Collection[str]]]) -> Callable[[T], T]:
    search_key_types = dict(search_key_types)

    # For each key type, we want to store the ordered attributes to query.
    # We accept attribute.sub_attribute, so split all target attributes on "."
    for key, types in search_key_types.items():
        if isinstance(types, str):
            types = [types]
        types = [type.split(".") for type in types]
        search_key_types[key] = types

    def search(instance: T, item: Any) -> bool:
        try:
            search_keys = search_key_types[type(item)]
        except KeyError:
            raise SearchError("Attempted to search for invalid type! Accepted types are {types}".format(types=[key_type.__name__ for key_type in search_key_types.keys()]))

        for search_key in search_keys:
            failed = False
            value = instance
            for sub_attribute in search_key:
                try:
                    value = getattr(value, sub_attribute)
                except AttributeError:
                    failed = True
                    break

            # This search key didn't exist for the item
            if failed:
                continue

            # Found the search item directly as an attribute
            if value == item:
                return True

            # Try to pass it along to the attribute's __contains__
            try:
                if item in value:
                    return True
            except (TypeError, SearchError):
                # The attribute doesn't define __contains__ or is searchable and doesn't accept this key type
                continue

    def decorator(cls: T) -> T:
        if hasattr(cls, "__contains__"):
            contains = cls.__contains__

            def new_contains(self, item: Any) -> bool:
                result = contains(self, item)

                # If it's contained by normal means, short circuit
                if result:
                    return True
                try:
                    # Try a search
                    if search(self, item):
                        return True
                except SearchError:
                    # Search doesn't accept that type
                    pass

                # Search and normal __contains__ implementations both were False
                return False
        else:
            new_contains = search

        cls.__contains__ = new_contains

        if cls.__doc__ is not None:
            cls.__doc__ += "\n\n"
        else:
            cls.__doc__ = ""
        cls.__doc__ += "Searchable by {types}".format(types=[key_type.__name__ for key_type in search_key_types.keys()])

        return cls

    return decorator

class SearchableList(list):
    def filter(self, function):
        return filter(function, self)

    def __getitem__(self, item: Any) -> Any:
        try:
            return super().__getitem__(item)
        except TypeError:
            return self.find(item)

    def __contains__(self, item: Any) -> bool:
        return self.contains(item)

    def __delitem__(self, item: Any) -> None:
        try:
            super().__delitem__(item)
        except TypeError:
            self.delete(item)

    def _search_generator(self, item: Any, reverse: bool = False) -> Generator[Any, None, None]:
        """A helper method for `self.search` that returns a generator rather than a list."""
        results = 0
        for _, x in self.enumerate(item, reverse=reverse):
            yield x
            results += 1
        if results == 0:
            raise SearchError(str(item))

    def search(self, item: Any, streaming: bool = False, reverse: bool = False) -> Union["SearchableList", Generator[Any, None, None]]:
        if streaming:
            return self._search_generator(item, reverse=reverse)
        else:
            result = SearchableList(x for _, x in self.enumerate(item, reverse=reverse))
            if len(result) == 0:
                raise SearchError(str(item))
            return result

    def find(self, item: Any, reverse: bool = False) -> Any:
        for _, x in self.enumerate(item, reverse=reverse):
            return x
        raise SearchError(str(item))

    def contains(self, item: Any) -> bool:
        for _, _ in self.enumerate(item):
            return True
        return False

    def enumerate(self, item: Any, reverse: bool = False) -> Generator[Tuple[int, Any], None, None]:
        items = self
        if reverse:
            max = len(items) - 1
            items = reversed(items)
        for index, x in enumerate(items):
            if x == item:
                yield max - index if reverse else index, x
                continue

            try:
                if item in x:
                    yield max - index if reverse else index, x
            except TypeError:
                # x doesn't define __contains__
                pass

    def delete(self, item: Any) -> None:
        deleted = 0
        for index, _ in self.enumerate(item, reverse=True):
            del self[index]
            deleted += 1
        if deleted == 0:
            raise SearchError(str(item))

class SearchableSet(set):
    def filter(self, function):
        return filter(function, self)

    def __getitem__(self, item: Any) -> Any:
        return self.find(item)

    def __contains__(self, item: Any) -> bool:
        return self.contains(item)

    def __delitem__(self, item: Any) -> None:
        self.delete(item)

    def _search_generator(self, item: Any) -> Generator[Any, None, None]:
        """A helper method for `self.search` that returns a generator rather than a list."""
        results = 0
        for x in self.enumerate(item):
            yield x
            results += 1
        if results == 0:
            raise SearchError(str(item))

    def search(self, item: Any, streaming: bool = False) -> Union["SearchableSet", Generator[Any, None, None]]:
        if streaming:
            return self._search_generator(item)
        else:
            result = SearchableSet(self.enumerate(item))
            if len(result) == 0:
                raise SearchError(str(item))
            return result

    def find(self, item: Any) -> Any:
        for x in self.enumerate(item):
            return x
        raise SearchError(str(item))

    def contains(self, item: Any) -> bool:
        for _ in self.enumerate(item):
            return True
        return False

    def enumerate(self, item: Any) -> Generator[Any, None, None]:
        for x in self:
            if x == item:
                yield x
                continue

            try:
                if item in x:
                    yield x
            except TypeError:
                # x doesn't define __contains__
                pass

    def delete(self, item: Any) -> None:
        to_delete = set(self.enumerate(item))
        if len(to_delete) == 0:
            raise SearchError(str(item))
        for x in to_delete:
            self.remove(x)

class SearchableDictionary(dict):
    def filter(self, function):
        return filter(function, self.items())

    def __getitem__(self, item: Any) -> Any:
        try:
            return super().__getitem__(item)
        except KeyError:
            return self.find(item)

    def __contains__(self, item: Any) -> bool:
        return self.contains(item)

    def __delitem__(self, item: Any) -> None:
        try:
            super().__delitem__(item)
        except KeyError:
            self.delete(item)

    def _search_generator(self, item: Any) -> Generator[Tuple[Any, Any], None, None]:
        """A helper method for `self.search` that returns a generator rather than a list."""
        results = 0
        for key, value in self.enumerate(item):
            yield key, value
            results += 1
        if results == 0:
            raise SearchError(str(item))

    def search(self, item: Any, streaming: bool = False) -> Union["SearchableDictionary", Generator[Tuple[Any, Any], None, None]]:
        if streaming:
            return self._search_generator(item)
        else:
            result = SearchableDictionary(self.enumerate(item))
            if len(result) == 0:
                raise SearchError(str(item))
            return result

    def find(self, item: Any) -> Tuple[Any, Any]:
        for key, value in self.items():
            if key == item:
                return key, value

            try:
                if item in key:
                    return key, value
            except TypeError:
                # key doesn't define __contains__
                pass

            if value == item:
                return key, value

            try:
                if item in value:
                    return key, value
            except TypeError:
                # value doesn't define __contains__
                pass
        raise SearchError(str(item))

    def contains(self, item: Any) -> bool:
        for _, _ in self.enumerate(item):
            return True
        return False

    def enumerate(self, item: Any) -> Generator[Tuple[Any, Any], None, None]:
        for key, value in self.items():
            if key == item:
                yield key, value
                continue

            try:
                if item in key:
                    yield key, value
                    continue
            except TypeError:
                # key doesn't define __contains__
                pass

            if value == item:
                yield key, value
                continue

            try:
                if item in value:
                    yield key, value
                    continue
            except TypeError:
                # value doesn't define __contains__
                pass

    def delete(self, item: Any) -> None:
        to_delete = {key for key, _ in self.enumerate(item)}
        if len(to_delete) == 0:
            raise SearchError(str(item))
        for key in to_delete:
            del self[key]

class SearchableLazyList(SearchableList):
    """A SearchableList where the values of the list are generated on-demand.

    Arguments:
        constructor (callable): A function that returns a generator of the values to be put in the list.
    """
    def __init__(self, generator: Generator):
        self._generator = generator
        self._empty = False
        super().__init__()  # initialize an empty list

    def __str__(self):
        if self._empty:
            return super().__str__()
        else:
            string = super().__str__()
            if string == "[]":
                return "[...]"
            else:
                return string[:-1] + ", ...]"

    def __iter__(self):
        for item in super().__iter__():
            yield item
        while not self._empty:
            yield next(self)

    def __len__(self):
        if self._empty:
            return super().__len__()
        else:
            raise RuntimeError("SearchableLazyList hasn't been fully generated yet and therefore has an unknown length.")

    def __next__(self):
        try:
            value = next(self._generator)
            self.append(value)
        except StopIteration as error:
            self._empty = True
            raise error
        return value

    def _generate_many(self, count: Optional[int] = None):
        if count is not None:
            for _ in range(count):
                next(self)
        else:
            for _ in self:
                pass
            assert self._empty

    def __getitem__(self, item: Any) -> Any:
        try:
            return list.__getitem__(self, item)
        except IndexError:
            # Generate new values until: 1) we get to position `item` (which is an int) or 2) no more values are left
            iterate_until = item - super().__len__() + 1
            try:
                self._generate_many(iterate_until)
            except StopIteration:
                pass
            # Now that we have 1) enough or 2) all the values, try returning again.
            # If we still get an index error, then we have an int that is being searched on.
            try:
                return list.__getitem__(self, item)
            except IndexError:
                return self.find(item)
        except TypeError:
            return self.find(item)

    def __delitem__(self, item: Any) -> None:
        if isinstance(item, int):
            # Make sure we have enough values generated
            try:
                iterate_until = item - super().__len__() + 1
                self._generate_many(iterate_until)
            except StopIteration:
                pass
        try:
            list.__delitem__(self, item)
        except (IndexError, TypeError) as error:
            self.delete(item)

    def __reversed__(self):
        self._generate_many()
        return super().__reversed__()

    def delete(self, item: Any) -> None:
        deleted = 0
        for index, _ in self.enumerate(item, reverse=False):
            del self[index]
            deleted += 1
        if deleted == 0:
            raise SearchError(str(item))
<|endoftext|>


<commit_before>import math, random

from panda3d.core import Vec3, Quat, GeomVertexFormat, NodePath

import meshManager
import gridFactory

noFerns=1

class FernFactory(gridFactory.GridFactory):
    def __init__(self,leafTexture=None,scalar=.25,gridSize=30.0):
        self.leafTexture=leafTexture
        gridFactory.GridFactory.__init__(self,scalar=scalar,gridSize=gridSize)

        self.leafDataIndex={}
        self.lowLOD=meshManager.LOD(1200,000)
        self.midLOD=meshManager.LOD(800,000)

    def getLODs(self):
        return [self.lowLOD,self.midLOD]

    def regesterGeomRequirements(self,LOD,collection):

        n=NodePath('tmp')

        if self.leafTexture is not None:
            n.setTexture(self.leafTexture)
            n.setShaderInput('diffTex',self.leafTexture)
            format=GeomVertexFormat.getV3n3t2()
        else:
            n.setColor(.1,.3,.1,1)
            format=GeomVertexFormat.getV3n3c4()

        #n.setShader(customLoader.makeShader(n,debugCodePrefix="fern",debugGraphPrefix="fern"))

        leafRequirements=meshManager.GeomRequirements(
                geomVertexFormat=GeomVertexFormat.getV3n3(),
                renderState=n.getState(),
                )

        self.leafDataIndex[LOD]=collection.add(leafRequirements)

    def drawItem(self,LOD,x,y,drawResourcesFactory,tile,tileCenter,collision,seed=True,scale=1.0):
        if seed: random.seed((x,y))
        exists=random.random()
        if exists<.6: return
        quat=Quat()

        pos=Vec3(x,y,tile.height(x,y))-tileCenter
        self.drawFern(LOD,pos, quat,drawResourcesFactory,scale=scale)

    def drawFern(self,LOD,pos,quat,drawResourcesFactory,scale=1.0):
        scalar=random.random()
        scale*=scalar

        if scale<.3: return

        count=int((scalar**.7)*12)

        if scale<.8:
            if LOD==self.lowLOD: return
        else:
            if LOD==self.midLOD: return

        leafResources=drawResourcesFactory.getDrawResources(self.leafDataIndex[LOD])
        leafTri=leafResources.getGeomTriangles()
        vertexWriter=leafResources.getWriter("vertex")
        normalWriter=leafResources.getWriter("normal")

        if self.leafTexture:
            texcoordWriter = leafResources.getWriter("texcoord")
#         else:
#             colorWriter = leafResources.getWriter("color")

        scale*=self.scalar*3

        q2=Quat()
        q3=Quat()

        for i in xrange(count):
            p=(random.random()**2)*60+20
            h=random.random()*360
            q2.setHpr((h,p,0))
            q3.setHpr((h,p-20-p/4,0))

            length1=scale*4
            length2=scale*3

            f=q2.getForward()*length1
            r=q2.getRight()*scale*.5
            f2=q3.getForward()*length2+f
            norm0=q2.getUp()
            norm2=q3.getUp()
            norm1=norm0+norm2
            norm1.normalize()

            for x in range(2):
                leafRow = vertexWriter.getWriteRow()

                vertexWriter.addData3f(pos)
                vertexWriter.addData3f(pos+f+r)
                vertexWriter.addData3f(pos+f-r)
                vertexWriter.addData3f(pos+f2)

                if self.leafTexture:
                    texcoordWriter.addData2f(0,0)
                    texcoordWriter.addData2f(0,1)
                    texcoordWriter.addData2f(1,0)
                    texcoordWriter.addData2f(1,1)
#                 else:
#                     colorWriter.addData4f(.1,.3,.1,1)
#                     colorWriter.addData4f(.1,.3,.1,1)
#                     colorWriter.addData4f(.1,.3,.1,1)
#                     colorWriter.addData4f(.1,.3,.1,1)

                if x==1:
                    # back sides
                    norm0=-norm0
                    norm1=-norm1
                    norm2=-norm2
                    leafTri.addVertices(leafRow+1,leafRow,leafRow+2)
                    leafTri.addVertices(leafRow+3,leafRow+1,leafRow+2)
                else:
                    leafTri.addVertices(leafRow,leafRow+1,leafRow+2)
                    leafTri.addVertices(leafRow+1,leafRow+3,leafRow+2)

                normalWriter.addData3f(norm0)
                normalWriter.addData3f(norm1)
                normalWriter.addData3f(norm1)
                normalWriter.addData3f(norm2)<commit_msg>fix for textured ferns<commit_after>import math, random

from panda3d.core import Vec3, Quat, GeomVertexFormat, NodePath

import meshManager
import gridFactory

noFerns=1

class FernFactory(gridFactory.GridFactory):
    def __init__(self,leafTexture=None,scalar=.25,gridSize=30.0):
        self.leafTexture=leafTexture
        gridFactory.GridFactory.__init__(self,scalar=scalar,gridSize=gridSize)

        self.leafDataIndex={}
        self.lowLOD=meshManager.LOD(1200,000)
        self.midLOD=meshManager.LOD(800,000)

    def getLODs(self):
        return [self.lowLOD,self.midLOD]

    def regesterGeomRequirements(self,LOD,collection):

        n=NodePath('tmp')

        if self.leafTexture is not None:
            n.setTexture(self.leafTexture)
            n.setShaderInput('diffTex',self.leafTexture)
            format=GeomVertexFormat.getV3n3t2()
        else:
            n.setColor(.1,.3,.1,1)
            format=GeomVertexFormat.getV3n3()

        #n.setShader(customLoader.makeShader(n,debugCodePrefix="fern",debugGraphPrefix="fern"))

        leafRequirements=meshManager.GeomRequirements(
                geomVertexFormat=format,
                renderState=n.getState(),
                )

        self.leafDataIndex[LOD]=collection.add(leafRequirements)

    def drawItem(self,LOD,x,y,drawResourcesFactory,tile,tileCenter,collision,seed=True,scale=1.0):
        if seed: random.seed((x,y))
        exists=random.random()
        if exists<.6: return
        quat=Quat()

        pos=Vec3(x,y,tile.height(x,y))-tileCenter
        self.drawFern(LOD,pos, quat,drawResourcesFactory,scale=scale)

    def drawFern(self,LOD,pos,quat,drawResourcesFactory,scale=1.0):
        scalar=random.random()
        scale*=scalar

        if scale<.3: return

        count=int((scalar**.7)*12)

        if scale<.8:
            if LOD==self.lowLOD: return
        else:
            if LOD==self.midLOD: return

        leafResources=drawResourcesFactory.getDrawResources(self.leafDataIndex[LOD])
        leafTri=leafResources.getGeomTriangles()
        vertexWriter=leafResources.getWriter("vertex")
        normalWriter=leafResources.getWriter("normal")

        if self.leafTexture:
            texcoordWriter = leafResources.getWriter("texcoord")

        scale*=self.scalar*3

        q2=Quat()
        q3=Quat()

        for i in xrange(count):
            p=(random.random()**2)*60+20
            h=random.random()*360
            q2.setHpr((h,p,0))
            q3.setHpr((h,p-20-p/4,0))

            length1=scale*4
            length2=scale*3

            f=q2.getForward()*length1
            r=q2.getRight()*scale*.5
            f2=q3.getForward()*length2+f
            norm0=q2.getUp()
            norm2=q3.getUp()
            norm1=norm0+norm2
            norm1.normalize()

            for x in range(2):
                leafRow = vertexWriter.getWriteRow()

                vertexWriter.addData3f(pos)
                vertexWriter.addData3f(pos+f+r)
                vertexWriter.addData3f(pos+f-r)
                vertexWriter.addData3f(pos+f2)

                if self.leafTexture:
                    texcoordWriter.addData2f(0,0)
                    texcoordWriter.addData2f(0,1)
                    texcoordWriter.addData2f(1,0)
                    texcoordWriter.addData2f(1,1)

                if x==1:
                    # back sides
                    norm0=-norm0
                    norm1=-norm1
                    norm2=-norm2
                    leafTri.addVertices(leafRow+1,leafRow,leafRow+2)
                    leafTri.addVertices(leafRow+3,leafRow+1,leafRow+2)
                else:
                    leafTri.addVertices(leafRow,leafRow+1,leafRow+2)
                    leafTri.addVertices(leafRow+1,leafRow+3,leafRow+2)

                normalWriter.addData3f(norm0)
                normalWriter.addData3f(norm1)
                normalWriter.addData3f(norm1)
                normalWriter.addData3f(norm2)<|endoftext|>


<commit_before>
number_words = [
    "one",
    "two",
    "three",
    "four",
    "five",
    "six",
    "seven",
    "eight",
    "nine",
    "ten",
    "teen",
    "eleven",
    "tweleve",
    "thirteen",
    "fifteen",
    "twenty",
    "thirty",
    "forty",
    "fifty",
    "sixty",
    "seventy",
    "eighty",
    "ninety",
    "hundred",
    "thousand"
]

result = 0

for i in range(1, 1001):

<commit_msg>Completed - Number Letter Counts<commit_after>
from num2words import num2words

result = 0

for i in range(1, 1001):
    num_word = num2words(i)
    num_word = num_word.replace(" ", "")
    num_word = num_word.replace("-", "")
    result += len(num_word)

print result
<|endoftext|>


<commit_before># Copyright 2016 The Meson development team

# 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.

from os import path
import shutil

from . import ExtensionModule, ModuleReturnValue
from .. import build
from .. import coredata
from .. import mesonlib
from .. import mlog
from ..interpreterbase import permittedKwargs, FeatureNew, FeatureNewKwargs
from ..mesonlib import MesonException
from ..scripts.gettext import read_linguas

PRESET_ARGS = {
    'glib': [
        '--from-code=UTF-8',
        '--add-comments',

        # https://developer.gnome.org/glib/stable/glib-I18N.html
        '--keyword=_',
        '--keyword=N_',
        '--keyword=C_:1c,2',
        '--keyword=NC_:1c,2',
        '--keyword=g_dcgettext:2',
        '--keyword=g_dngettext:2,3',
        '--keyword=g_dpgettext2:2c,3',

        '--flag=N_:1:pass-c-format',
        '--flag=C_:2:pass-c-format',
        '--flag=NC_:2:pass-c-format',
        '--flag=g_dngettext:2:pass-c-format',
        '--flag=g_strdup_printf:1:c-format',
        '--flag=g_string_printf:2:c-format',
        '--flag=g_string_append_printf:2:c-format',
        '--flag=g_error_new:3:c-format',
        '--flag=g_set_error:4:c-format',
        '--flag=g_markup_printf_escaped:1:c-format',
        '--flag=g_log:3:c-format',
        '--flag=g_print:1:c-format',
        '--flag=g_printerr:1:c-format',
        '--flag=g_printf:1:c-format',
        '--flag=g_fprintf:2:c-format',
        '--flag=g_sprintf:2:c-format',
        '--flag=g_snprintf:3:c-format',
    ]
}

class I18nModule(ExtensionModule):
    def __init__(self, interpreter):
        super().__init__(interpreter)
        self.methods.update({
            'merge_file': self.merge_file,
            'gettext': self.gettext,
        })

    @staticmethod
    def nogettext_warning():
        mlog.warning('Gettext not found, all translation targets will be ignored.', once=True)

    @staticmethod
    def _get_data_dirs(state, dirs):
        """Returns source directories of relative paths"""
        src_dir = path.join(state.environment.get_source_dir(), state.subdir)
        return [path.join(src_dir, d) for d in dirs]

    @FeatureNew('i18n.merge_file', '0.37.0')
    @FeatureNewKwargs('i18n.merge_file', '0.51.0', ['args'])
    @permittedKwargs(build.CustomTarget.known_kwargs | {'data_dirs', 'po_dir', 'type', 'args'})
    def merge_file(self, state, args, kwargs):
        if not shutil.which('xgettext'):
            self.nogettext_warning()
            return
        podir = kwargs.pop('po_dir', None)
        if not podir:
            raise MesonException('i18n: po_dir is a required kwarg')
        podir = path.join(state.build_to_src, state.subdir, podir)

        file_type = kwargs.pop('type', 'xml')
        VALID_TYPES = ('xml', 'desktop')
        if file_type not in VALID_TYPES:
            raise MesonException(f'i18n: "{file_type}" is not a valid type {VALID_TYPES}')

        datadirs = self._get_data_dirs(state, mesonlib.stringlistify(kwargs.pop('data_dirs', [])))
        datadirs = '--datadirs=' + ':'.join(datadirs) if datadirs else None

        command = state.environment.get_build_command() + [
            '--internal', 'msgfmthelper',
            '@INPUT@', '@OUTPUT@', file_type, podir
        ]
        if datadirs:
            command.append(datadirs)

        if 'args' in kwargs:
            command.append('--')
            command.append(mesonlib.stringlistify(kwargs.pop('args', [])))

        kwargs['command'] = command

        # We only use this input file to create a name of the custom target.
        # Thus we can ignore the other entries.
        inputfile = mesonlib.extract_as_list(kwargs, 'input')[0]
        if isinstance(inputfile, str):
            inputfile = mesonlib.File.from_source_file(state.environment.source_dir,
                                                       state.subdir, inputfile)
        if isinstance(inputfile, mesonlib.File):
            # output could be '@BASENAME@' in which case we need to do substitutions
            # to get a unique target name.
            output = kwargs['output']
            ifile_abs = inputfile.absolute_path(state.environment.source_dir,
                                                state.environment.build_dir)
            values = mesonlib.get_filenames_templates_dict([ifile_abs], None)
            outputs = mesonlib.substitute_values([output], values)
            output = outputs[0]
            ct = build.CustomTarget(output + '_' + state.subdir.replace('/', '@').replace('\\', '@') + '_merge', state.subdir, state.subproject, kwargs)
        else:
            ct = build.CustomTarget(kwargs['output'] + '_merge', state.subdir, state.subproject, kwargs)

        return ModuleReturnValue(ct, [ct])

    @FeatureNewKwargs('i18n.gettext', '0.37.0', ['preset'])
    @FeatureNewKwargs('i18n.gettext', '0.50.0', ['install_dir'])
    @permittedKwargs({'po_dir', 'data_dirs', 'type', 'languages', 'args', 'preset', 'install', 'install_dir'})
    def gettext(self, state, args, kwargs):
        if len(args) != 1:
            raise coredata.MesonException('Gettext requires one positional argument (package name).')
        if not shutil.which('xgettext'):
            self.nogettext_warning()
            return
        packagename = args[0]
        languages = mesonlib.stringlistify(kwargs.get('languages', []))
        datadirs = self._get_data_dirs(state, mesonlib.stringlistify(kwargs.get('data_dirs', [])))
        extra_args = mesonlib.stringlistify(kwargs.get('args', []))
        targets = []
        gmotargets = []

        preset = kwargs.pop('preset', None)
        if preset:
            preset_args = PRESET_ARGS.get(preset)
            if not preset_args:
                raise coredata.MesonException('i18n: Preset "{}" is not one of the valid options: {}'.format(
                                              preset, list(PRESET_ARGS.keys())))
            extra_args = set(preset_args + extra_args)

        pkg_arg = '--pkgname=' + packagename
        lang_arg = '--langs=' + '@@'.join(languages) if languages else None
        datadirs = '--datadirs=' + ':'.join(datadirs) if datadirs else None
        extra_args = '--extra-args=' + '@@'.join(extra_args) if extra_args else None

        potargs = state.environment.get_build_command() + ['--internal', 'gettext', 'pot', pkg_arg]
        if datadirs:
            potargs.append(datadirs)
        if extra_args:
            potargs.append(extra_args)
        pottarget = build.RunTarget(packagename + '-pot', potargs, [], state.subdir, state.subproject)
        targets.append(pottarget)

        install = kwargs.get('install', True)
        install_dir = kwargs.get('install_dir', state.environment.coredata.get_option(mesonlib.OptionKey('localedir')))
        if not languages:
            languages = read_linguas(path.join(state.environment.source_dir, state.subdir))
        for l in languages:
            po_file = mesonlib.File.from_source_file(state.environment.source_dir,
                                                     state.subdir, l+'.po')
            gmo_kwargs = {'command': ['msgfmt', '@INPUT@', '-o', '@OUTPUT@'],
                          'input': po_file,
                          'output': packagename+'.mo',
                          'install': install,
                          # We have multiple files all installed as packagename+'.mo' in different install subdirs.
                          # What we really wanted to do, probably, is have a rename: kwarg, but that's not available
                          # to custom_targets. Crude hack: set the build target's subdir manually.
                          # Bonus: the build tree has something usable as an uninstalled bindtextdomain() target dir.
                          'install_dir': path.join(install_dir, l, 'LC_MESSAGES'),
                          'install_tag': 'i18n',
                          }
            gmotarget = build.CustomTarget(f'{packagename}-{l}.mo', path.join(state.subdir, l, 'LC_MESSAGES'), state.subproject, gmo_kwargs)
            targets.append(gmotarget)
            gmotargets.append(gmotarget)

        allgmotarget = build.AliasTarget(packagename + '-gmo', gmotargets, state.subdir, state.subproject)
        targets.append(allgmotarget)

        updatepoargs = state.environment.get_build_command() + ['--internal', 'gettext', 'update_po', pkg_arg]
        if lang_arg:
            updatepoargs.append(lang_arg)
        if datadirs:
            updatepoargs.append(datadirs)
        if extra_args:
            updatepoargs.append(extra_args)
        updatepotarget = build.RunTarget(packagename + '-update-po', updatepoargs, [], state.subdir, state.subproject)
        targets.append(updatepotarget)

        return ModuleReturnValue([gmotargets, pottarget, updatepotarget], targets)

def initialize(*args, **kwargs):
    return I18nModule(*args, **kwargs)
<commit_msg>modules/i18n: add easy type annotations<commit_after># Copyright 2016 The Meson development team

# 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.

from os import path
import shutil
import typing as T

from . import ExtensionModule, ModuleReturnValue
from .. import build
from .. import coredata
from .. import mesonlib
from .. import mlog
from ..interpreterbase import permittedKwargs, FeatureNew, FeatureNewKwargs
from ..mesonlib import MesonException
from ..scripts.gettext import read_linguas

if T.TYPE_CHECKING:
    from . import ModuleState
    from ..interpreter import Interpreter

PRESET_ARGS = {
    'glib': [
        '--from-code=UTF-8',
        '--add-comments',

        # https://developer.gnome.org/glib/stable/glib-I18N.html
        '--keyword=_',
        '--keyword=N_',
        '--keyword=C_:1c,2',
        '--keyword=NC_:1c,2',
        '--keyword=g_dcgettext:2',
        '--keyword=g_dngettext:2,3',
        '--keyword=g_dpgettext2:2c,3',

        '--flag=N_:1:pass-c-format',
        '--flag=C_:2:pass-c-format',
        '--flag=NC_:2:pass-c-format',
        '--flag=g_dngettext:2:pass-c-format',
        '--flag=g_strdup_printf:1:c-format',
        '--flag=g_string_printf:2:c-format',
        '--flag=g_string_append_printf:2:c-format',
        '--flag=g_error_new:3:c-format',
        '--flag=g_set_error:4:c-format',
        '--flag=g_markup_printf_escaped:1:c-format',
        '--flag=g_log:3:c-format',
        '--flag=g_print:1:c-format',
        '--flag=g_printerr:1:c-format',
        '--flag=g_printf:1:c-format',
        '--flag=g_fprintf:2:c-format',
        '--flag=g_sprintf:2:c-format',
        '--flag=g_snprintf:3:c-format',
    ]
}

class I18nModule(ExtensionModule):
    def __init__(self, interpreter: 'Interpreter'):
        super().__init__(interpreter)
        self.methods.update({
            'merge_file': self.merge_file,
            'gettext': self.gettext,
        })

    @staticmethod
    def nogettext_warning() -> None:
        mlog.warning('Gettext not found, all translation targets will be ignored.', once=True)

    @staticmethod
    def _get_data_dirs(state: 'ModuleState', dirs: T.Iterable[str]) -> T.List[str]:
        """Returns source directories of relative paths"""
        src_dir = path.join(state.environment.get_source_dir(), state.subdir)
        return [path.join(src_dir, d) for d in dirs]

    @FeatureNew('i18n.merge_file', '0.37.0')
    @FeatureNewKwargs('i18n.merge_file', '0.51.0', ['args'])
    @permittedKwargs(build.CustomTarget.known_kwargs | {'data_dirs', 'po_dir', 'type', 'args'})
    def merge_file(self, state: 'ModuleState', args, kwargs) -> ModuleReturnValue:
        if not shutil.which('xgettext'):
            self.nogettext_warning()
            return
        podir = kwargs.pop('po_dir', None)
        if not podir:
            raise MesonException('i18n: po_dir is a required kwarg')
        podir = path.join(state.build_to_src, state.subdir, podir)

        file_type = kwargs.pop('type', 'xml')
        VALID_TYPES = ('xml', 'desktop')
        if file_type not in VALID_TYPES:
            raise MesonException(f'i18n: "{file_type}" is not a valid type {VALID_TYPES}')

        datadirs = self._get_data_dirs(state, mesonlib.stringlistify(kwargs.pop('data_dirs', [])))
        datadirs = '--datadirs=' + ':'.join(datadirs) if datadirs else None

        command = state.environment.get_build_command() + [
            '--internal', 'msgfmthelper',
            '@INPUT@', '@OUTPUT@', file_type, podir
        ]
        if datadirs:
            command.append(datadirs)

        if 'args' in kwargs:
            command.append('--')
            command.append(mesonlib.stringlistify(kwargs.pop('args', [])))

        kwargs['command'] = command

        # We only use this input file to create a name of the custom target.
        # Thus we can ignore the other entries.
        inputfile = mesonlib.extract_as_list(kwargs, 'input')[0]
        if isinstance(inputfile, str):
            inputfile = mesonlib.File.from_source_file(state.environment.source_dir,
                                                       state.subdir, inputfile)
        if isinstance(inputfile, mesonlib.File):
            # output could be '@BASENAME@' in which case we need to do substitutions
            # to get a unique target name.
            output = kwargs['output']
            ifile_abs = inputfile.absolute_path(state.environment.source_dir,
                                                state.environment.build_dir)
            values = mesonlib.get_filenames_templates_dict([ifile_abs], None)
            outputs = mesonlib.substitute_values([output], values)
            output = outputs[0]
            ct = build.CustomTarget(output + '_' + state.subdir.replace('/', '@').replace('\\', '@') + '_merge', state.subdir, state.subproject, kwargs)
        else:
            ct = build.CustomTarget(kwargs['output'] + '_merge', state.subdir, state.subproject, kwargs)

        return ModuleReturnValue(ct, [ct])

    @FeatureNewKwargs('i18n.gettext', '0.37.0', ['preset'])
    @FeatureNewKwargs('i18n.gettext', '0.50.0', ['install_dir'])
    @permittedKwargs({'po_dir', 'data_dirs', 'type', 'languages', 'args', 'preset', 'install', 'install_dir'})
    def gettext(self, state: 'ModuleState', args, kwargs) -> ModuleReturnValue:
        if len(args) != 1:
            raise coredata.MesonException('Gettext requires one positional argument (package name).')
        if not shutil.which('xgettext'):
            self.nogettext_warning()
            return
        packagename = args[0]
        languages = mesonlib.stringlistify(kwargs.get('languages', []))
        datadirs = self._get_data_dirs(state, mesonlib.stringlistify(kwargs.get('data_dirs', [])))
        extra_args = mesonlib.stringlistify(kwargs.get('args', []))
        targets = []
        gmotargets = []

        preset = kwargs.pop('preset', None)
        if preset:
            preset_args = PRESET_ARGS.get(preset)
            if not preset_args:
                raise coredata.MesonException('i18n: Preset "{}" is not one of the valid options: {}'.format(
                                              preset, list(PRESET_ARGS.keys())))
            extra_args = set(preset_args + extra_args)

        pkg_arg = '--pkgname=' + packagename
        lang_arg = '--langs=' + '@@'.join(languages) if languages else None
        datadirs = '--datadirs=' + ':'.join(datadirs) if datadirs else None
        extra_args = '--extra-args=' + '@@'.join(extra_args) if extra_args else None

        potargs = state.environment.get_build_command() + ['--internal', 'gettext', 'pot', pkg_arg]
        if datadirs:
            potargs.append(datadirs)
        if extra_args:
            potargs.append(extra_args)
        pottarget = build.RunTarget(packagename + '-pot', potargs, [], state.subdir, state.subproject)
        targets.append(pottarget)

        install = kwargs.get('install', True)
        install_dir = kwargs.get('install_dir', state.environment.coredata.get_option(mesonlib.OptionKey('localedir')))
        if not languages:
            languages = read_linguas(path.join(state.environment.source_dir, state.subdir))
        for l in languages:
            po_file = mesonlib.File.from_source_file(state.environment.source_dir,
                                                     state.subdir, l+'.po')
            gmo_kwargs = {'command': ['msgfmt', '@INPUT@', '-o', '@OUTPUT@'],
                          'input': po_file,
                          'output': packagename+'.mo',
                          'install': install,
                          # We have multiple files all installed as packagename+'.mo' in different install subdirs.
                          # What we really wanted to do, probably, is have a rename: kwarg, but that's not available
                          # to custom_targets. Crude hack: set the build target's subdir manually.
                          # Bonus: the build tree has something usable as an uninstalled bindtextdomain() target dir.
                          'install_dir': path.join(install_dir, l, 'LC_MESSAGES'),
                          'install_tag': 'i18n',
                          }
            gmotarget = build.CustomTarget(f'{packagename}-{l}.mo', path.join(state.subdir, l, 'LC_MESSAGES'), state.subproject, gmo_kwargs)
            targets.append(gmotarget)
            gmotargets.append(gmotarget)

        allgmotarget = build.AliasTarget(packagename + '-gmo', gmotargets, state.subdir, state.subproject)
        targets.append(allgmotarget)

        updatepoargs = state.environment.get_build_command() + ['--internal', 'gettext', 'update_po', pkg_arg]
        if lang_arg:
            updatepoargs.append(lang_arg)
        if datadirs:
            updatepoargs.append(datadirs)
        if extra_args:
            updatepoargs.append(extra_args)
        updatepotarget = build.RunTarget(packagename + '-update-po', updatepoargs, [], state.subdir, state.subproject)
        targets.append(updatepotarget)

        return ModuleReturnValue([gmotargets, pottarget, updatepotarget], targets)

def initialize(interp: 'Interpreter') -> I18nModule:
    return I18nModule(interp)
<|endoftext|>


<commit_before>#!/usr/bin/env python3

# Copyright (C) 2011 Victor Semionov
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#  * Redistributions of source code must retain the above copyright notice, this
#    list of conditions and the following disclaimer.
#  * Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#  * Neither the name of the copyright holder nor the names of the contributors
#    may be used to endorse or promote products derived from this software
#    without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import sys
import os

import psycopg2

import pgutil

create_schema = "CREATE SCHEMA {};"

create_dictionaries = "CREATE TABLE {}.dictionaries (" \
                        "id SERIAL PRIMARY KEY, " \
                        "match_order INTEGER, " \
                        "name VARCHAR(32) UNIQUE NOT NULL, " \
                        "short_desc VARCHAR(128) NOT NULL, " \
                        "info TEXT" \
                        ");"

create_definitions = "CREATE TABLE {0}.definitions (" \
                        "id SERIAL PRIMARY KEY, " \
                        "dict_id INTEGER NOT NULL REFERENCES {0}.dictionaries, " \
                        "word VARCHAR(64) NOT NULL, " \
                        "definition TEXT NOT NULL" \
                        ");"

create_virtual_dictionaries = "CREATE TABLE {}.virtual_dictionaries (" \
                                "id SERIAL PRIMARY KEY, " \
                                "name VARCHAR(32) UNIQUE NOT NULL, " \
                                "short_desc VARCHAR(128) NOT NULL" \
                                ");" \

create_virtual_dictionary_items = "CREATE TABLE {0}.virtual_dictionary_items (" \
                                    "virt_id INTEGER NOT NULL REFERENCES {0}.virtual_dictionaries, " \
                                    "dict_id INTEGER NOT NULL REFERENCES {0}.dictionaries, " \
                                    "PRIMARY KEY (virt_id, dict_id)" \
                                    ");" \

script_name = os.path.basename(__file__)

def usage():
    print("Usage: {} [-f conf_file]".format(script_name), file=sys.stderr)
    print("Initializes a wordbase pgsql schema.", file=sys.stderr)

def init_pgsql_task(cur, schema):
    if schema != "public":
        cur.execute(create_schema.format(schema))
    cur.execute(create_dictionaries.format(schema))
    cur.execute(create_definitions.format(schema))
    cur.execute(create_virtual_dictionaries.format(schema))
    cur.execute(create_virtual_dictionary_items.format(schema))

pgutil.get_pgsql_params(None, 0, usage)

pgutil.process_pgsql_task(init_pgsql_task)
<commit_msg>Schema with inheritance.<commit_after>#!/usr/bin/env python3

# Copyright (C) 2011 Victor Semionov
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#  * Redistributions of source code must retain the above copyright notice, this
#    list of conditions and the following disclaimer.
#  * Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#  * Neither the name of the copyright holder nor the names of the contributors
#    may be used to endorse or promote products derived from this software
#    without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import sys
import os

import psycopg2

import pgutil

create_schema = "CREATE SCHEMA {};"

create_dictionaries_base = "CREATE TABLE {}.dictionaries_base (" \
                            "id SERIAL PRIMARY KEY, " \
                            "name VARCHAR(32) UNIQUE NOT NULL, " \
                            "short_desc VARCHAR(128) NOT NULL" \
                            ");"

create_dictionaries = "CREATE TABLE {0}.dictionaries (" \
                        "id SERIAL PRIMARY KEY, " \
                        "match_order INTEGER, " \
                        "info TEXT" \
                        ") INHERITS ({0}.dictionaries_base);"

create_virtual_dictionaries = "CREATE TABLE {0}.virtual_dictionaries (" \
                                "id SERIAL PRIMARY KEY" \
                                ") INHERITS ({0}.dictionaries_base);" \

create_definitions = "CREATE TABLE {0}.definitions (" \
                        "id SERIAL PRIMARY KEY, " \
                        "dict_id INTEGER NOT NULL REFERENCES {0}.dictionaries, " \
                        "word VARCHAR(64) NOT NULL, " \
                        "definition TEXT NOT NULL" \
                        ");"

create_virtual_dictionary_items = "CREATE TABLE {0}.virtual_dictionary_items (" \
                                    "virt_id INTEGER NOT NULL REFERENCES {0}.virtual_dictionaries, " \
                                    "dict_id INTEGER NOT NULL REFERENCES {0}.dictionaries, " \
                                    "PRIMARY KEY (virt_id, dict_id)" \
                                    ");" \

script_name = os.path.basename(__file__)

def usage():
    print("Usage: {} [-f conf_file]".format(script_name), file=sys.stderr)
    print("Initializes a wordbase pgsql schema.", file=sys.stderr)

def init_pgsql_task(cur, schema):
    if schema != "public":
        cur.execute(create_schema.format(schema))
    cur.execute(create_dictionaries_base.format(schema))
    cur.execute(create_dictionaries.format(schema))
    cur.execute(create_virtual_dictionaries.format(schema))
    cur.execute(create_definitions.format(schema))
    cur.execute(create_virtual_dictionary_items.format(schema))

pgutil.get_pgsql_params(None, 0, usage)

pgutil.process_pgsql_task(init_pgsql_task)
<|endoftext|>