diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f3d74a9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.pyc +*~ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..976cf4f --- /dev/null +++ b/LICENSE @@ -0,0 +1,25 @@ +Copyright 2012 Aleksey Yeletsky. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are +permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + + 2. 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. + +THIS SOFTWARE IS PROVIDED BY ``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 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. + +The views and conclusions contained in the software and documentation are those of the +authors and should not be interpreted as representing official policies, either expressed +or implied, of . \ No newline at end of file diff --git a/pynames/__init__.py b/pynames/__init__.py new file mode 100644 index 0000000..57d631c --- /dev/null +++ b/pynames/__init__.py @@ -0,0 +1 @@ +# coding: utf-8 diff --git a/pynames/fixtures/test_from_list_generator.json b/pynames/fixtures/test_from_list_generator.json new file mode 100644 index 0000000..e0a20a2 --- /dev/null +++ b/pynames/fixtures/test_from_list_generator.json @@ -0,0 +1,32 @@ +{ + "names": [ {"native_language": "ru", + "genders": {"m": {"ru": "ru_m_name_1", + "en": "en_m_name_1"}}}, + + {"native_language": "ru", + "genders": {"f": {"ru": "ru_f_name_2", + "en": "en_f_name_2"}}}, + + {"native_language": "ru", + "genders": {"f": {"ru": "ru_f_name_3", + "en": "en_f_name_3"}}}, + + {"native_language": "ru", + "genders": {"m": {"ru": "ru_m_name_4", + "en": "en_m_name_4"}, + "f": {"ru": "ru_f_name_4", + "en": "en_f_name_4"}}}, + + {"native_language": "ru", + "genders": {"m": {"ru": "ru_m_name_5", + "en": "en_m_name_5"}, + "f": {"ru": "ru_f_name_5", + "en": "en_f_name_5"}}}, + + {"native_language": "ru", + "genders": {"m": {"ru": "ru_m_name_5", + "en": "en_m_name_5"}, + "f": {"ru": "ru_f_name_5", + "en": "en_f_name_5"}}} + ] +} \ No newline at end of file diff --git a/pynames/generators.py b/pynames/generators.py new file mode 100644 index 0000000..de8c1ea --- /dev/null +++ b/pynames/generators.py @@ -0,0 +1,98 @@ +# coding: utf-8 + +import json +import random + + +class PynamesException(Exception): + pass + + +class GENDER: + MALE = 'm' + FEMALE = 'f' + MF = ['m', 'f'] + + ALL = ['m', 'f'] + + +class LANGUAGE: + RU = 'ru' + EN = 'en' + NATIVE = 'native' + + ALL = ['ru', 'en', 'native'] + + +class BaseGenerator(object): + pass + + +class Name(object): + + __slots__ = ('genders', 'native_language', 'translations') + + def __init__(self, data): + self.native_language = data['native_language'] + self.genders = frozenset(data['genders'].keys()) + self.translations = data['genders'] + + def get_for(self, gender, language=LANGUAGE.NATIVE): + if language == LANGUAGE.NATIVE: + language = self.native_language + return self.translations[gender][language] + + def exists_for(self, genders): + return genders & self.genders + + def __unicode__(self): + for gender in GENDER.ALL: + if gender in self.genders: + return self.translations[gender][self.native_language] + error_msg = 'Name: can not get default value for name with data: %r' % self.genders + raise PynamesException(error_msg) + + def __str__(self): return self.__unicode__() + + +class FromListGenerator(BaseGenerator): + + SOURCE = None + + def __init__(self): + self.names_list = [] + self.choices = {} + + if self.SOURCE is None: + error_msg = 'FromListGenerator: you must make subclass of FromListGenerator and defined attribute SOURCE in it.' + raise NotImplementedError(error_msg) + + with open(self.SOURCE) as f: + names_data = json.load(f) + for name_data in names_data['names']: + self.names_list.append(Name(name_data)) + + if not self.names_list: + raise PynamesException('FromListGenerator: no names loaded from "%s"' % self.SOURCE) + + def _get_cache_key(self, genders): + return '_'.join(genders) + + def _get_slice(self, genders): + key = self._get_cache_key(genders) + genders = frozenset(genders) + if key not in self.choices: + self.choices[key] = [name_record + for name_record in self.names_list + if name_record.exists_for(genders)] + return self.choices[key] + + def get_names_number(self, genders=GENDER.ALL): + return len(self._get_slice(genders)) + + def get_name(self, genders=GENDER.ALL): + return random.choice(self._get_slice(genders)) + + def get_name_simple(self, gender=GENDER.MALE, language=LANGUAGE.NATIVE): + name = self.get_name(genders=[gender]) + return name.get_for(gender, language) diff --git a/pynames/russian/__init__.py b/pynames/russian/__init__.py new file mode 100644 index 0000000..0fa2f8b --- /dev/null +++ b/pynames/russian/__init__.py @@ -0,0 +1,5 @@ +# coding: utf-8 + +import os + + diff --git a/pynames/tests.py b/pynames/tests.py new file mode 100644 index 0000000..8c74730 --- /dev/null +++ b/pynames/tests.py @@ -0,0 +1,87 @@ +# coding: utf-8 + +import os +import unittest + +from .generators import Name, GENDER, LANGUAGE, FromListGenerator, PynamesException + +FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixtures') + +class TestName(unittest.TestCase): + + def test_base(self): + name = Name({'native_language': 'ru', + 'genders': {'m': {'ru': 'ru_name'}}}) + self.assertEqual(unicode(name), 'ru_name') + self.assertEqual(name.get_for(GENDER.MALE, LANGUAGE.RU), 'ru_name') + self.assertEqual(name.get_for(GENDER.MALE), 'ru_name') + + def test_genders(self): + name = Name({'native_language': 'ru', + 'genders': {'m': {'ru': 'ru_m_name'}, + 'f': {'ru': 'ru_f_name'}}}) + self.assertEqual(unicode(name), 'ru_m_name') + self.assertEqual(name.get_for(GENDER.MALE, LANGUAGE.RU), 'ru_m_name') + self.assertEqual(name.get_for(GENDER.FEMALE, LANGUAGE.RU), 'ru_f_name') + + def test_languages(self): + name = Name({'native_language': 'ru', + 'genders': {'m': {'ru': 'ru_m_name', + 'en': 'en_m_name'}, + 'f': {'ru': 'ru_f_name', + 'en': 'en_f_name'}}}) + self.assertEqual(unicode(name), 'ru_m_name') + self.assertEqual(name.get_for(GENDER.MALE, LANGUAGE.RU), 'ru_m_name') + self.assertEqual(name.get_for(GENDER.FEMALE, LANGUAGE.RU), 'ru_f_name') + self.assertEqual(name.get_for(GENDER.MALE, LANGUAGE.EN), 'en_m_name') + self.assertEqual(name.get_for(GENDER.FEMALE, LANGUAGE.EN), 'en_f_name') + self.assertEqual(name.get_for(GENDER.MALE), 'ru_m_name') + self.assertEqual(name.get_for(GENDER.FEMALE), 'ru_f_name') + + +class TestFromListGenerator(unittest.TestCase): + + class TestGenerator(FromListGenerator): + SOURCE = os.path.join(FIXTURES_DIR, 'test_from_list_generator.json') + + NAMES_RU_MALE = ['ru_m_name_1', 'ru_m_name_4', 'ru_m_name_5', 'ru_m_name_6'] + NAMES_EN_MALE = ['en_m_name_1', 'en_m_name_4', 'en_m_name_5', 'en_m_name_6'] + NAMES_RU_FEMALE = ['ru_f_name_2', 'ru_f_name_3', 'ru_f_name_4', 'ru_f_name_5', 'ru_f_name_6'] + NAMES_EN_FEMALE = ['en_f_name_2', 'en_f_name_3', 'en_f_name_4', 'en_f_name_5', 'en_f_name_6'] + + def test_not_derived(self): + self.assertRaises(NotImplementedError, FromListGenerator) + + def test_wrong_path(self): + class WrongGenerator(FromListGenerator): + SOURCE = '' + self.assertRaises(IOError, WrongGenerator) + + def test_base(self): + generator = self.TestGenerator() + self.assertEqual(generator.get_names_number(), 6) + self.assertTrue(generator.get_name_simple() in self.NAMES_RU_MALE) + + def test_male_female_selection(self): + generator = self.TestGenerator() + self.assertEqual(generator.get_names_number(genders=[GENDER.MALE]), 4) + self.assertEqual(generator.get_names_number(genders=[GENDER.FEMALE]), 5) + + def test_get_name(self): + generator = self.TestGenerator() + + for i in xrange(100): + name = generator.get_name_simple(gender=GENDER.MALE, language=LANGUAGE.RU) + self.assertTrue(name in self.NAMES_RU_MALE) + + for i in xrange(100): + name = generator.get_name_simple(gender=GENDER.FEMALE, language=LANGUAGE.RU) + self.assertTrue(name in self.NAMES_RU_FEMALE) + + for i in xrange(100): + name = generator.get_name_simple(gender=GENDER.MALE, language=LANGUAGE.EN) + self.assertTrue(name in self.NAMES_EN_MALE) + + for i in xrange(100): + name = generator.get_name_simple(gender=GENDER.FEMALE, language=LANGUAGE.EN) + self.assertTrue(name in self.NAMES_EN_FEMALE)