Skip to content

Commit 9ad1799

Browse files
committed
Add generation method and update version to 0.1.4
This commit adds a generation function and random function with date ranges.
1 parent ec5eb42 commit 9ad1799

File tree

8 files changed

+196
-86
lines changed

8 files changed

+196
-86
lines changed

.flake8

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[flake8]
2+
exclude = .git,__pycache__,docs/source/conf.py,old,build,dist
3+
max-line-length = 120
4+
max-complexity = 20

.travis.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
language: python
22
python:
3-
- "2.7"
4-
- "3.6"
3+
- "3.8"
54
deploy:
65
provider: pypi
76
user: miglen
@@ -15,7 +14,7 @@ notifications:
1514
install:
1615
- pip install -r test/requirements.txt
1716
- pip install .
18-
script:
17+
script:
1918
- pep8
2019
- pytest --cov=./egn/
2120
after_success:

README.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ Python package
4949
>>> ....
5050

5151
# Generating with options
52-
egn_options = {'year': 1999, 'month': 3, 'day': 3, 'region': 'Sofia', 'sex': 'm'}
52+
egn_options = {'year': 1999, 'month': 3, 'day': 3, 'region': 'Sofia', 'gender': 'm'}
5353
egn.generate(egn_options)
5454
>>> ....
5555

@@ -70,7 +70,7 @@ Here are the most common commands:
7070

7171
# Parse
7272
$ egn -p 1234567890
73-
73+
7474
# Generate
7575
$ egn -g # random without options
7676
$ egn -g -y 1999 -m 3 -d 3 -r Sofia -s male # with options

egn/__init__.py

Lines changed: 168 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -2,45 +2,150 @@
22
import argparse
33
import sys
44
import random
5+
from datetime import timedelta, datetime
6+
import json
57

68
egn_regions = {
7-
43: {'bg': 'Благоевград', 'en': 'Blagoevgrad'}, # 000 - 043
8-
93: {'bg': 'Бургас', 'en': 'Burgas'}, # 044 - 093
9-
139: {'bg': 'Варна', 'en': 'Varna'}, # 094 - 139
10-
169: {'bg': 'Велико Търново', 'en': 'Veliko Turnovo'}, # 140 - 169
11-
183: {'bg': 'Видин', 'en': 'Vidin'}, # 170 - 183
12-
217: {'bg': 'Враца', 'en': 'Vratza'}, # 184 - 217
13-
233: {'bg': 'Габрово', 'en': 'Gabrovo'}, # 218 - 233
14-
281: {'bg': 'Кърджали', 'en': 'Kurdzhali'}, # 234 - 281
15-
301: {'bg': 'Кюстендил', 'en': 'Kyustendil'}, # 282 - 301
16-
319: {'bg': 'Ловеч', 'en': 'Lovech'}, # 302 - 319
17-
341: {'bg': 'Монтана', 'en': 'Montana'}, # 320 - 341
18-
377: {'bg': 'Пазарджик', 'en': 'Pazardzhik'}, # 342 - 377
19-
395: {'bg': 'Перник', 'en': 'Pernik'}, # 378 - 395
20-
435: {'bg': 'Плевен', 'en': 'Pleven'}, # 396 - 435
21-
501: {'bg': 'Пловдив', 'en': 'Plovdiv'}, # 436 - 501
22-
527: {'bg': 'Разград', 'en': 'Razgrad'}, # 502 - 527
23-
555: {'bg': 'Русе', 'en': 'Ruse'}, # 528 - 555
24-
575: {'bg': 'Силистра', 'en': 'Silistra'}, # 556 - 575
25-
601: {'bg': 'Сливен', 'en': 'Sliven'}, # 576 - 601
26-
623: {'bg': 'Смолян', 'en': 'Smolyan'}, # 602 - 623
27-
721: {'bg': 'София', 'en': 'Sofia'}, # 624 - 721
28-
751: {'bg': 'София (окръг)', 'en': 'Sofia (county)'}, # 722 - 751
29-
789: {'bg': 'Стара Загора', 'en': 'Stara Zagora'}, # 752 - 789
30-
821: {'bg': 'Добрич', 'en': 'Dobrich'}, # 790 - 821
31-
843: {'bg': 'Търговище', 'en': 'Targovishte'}, # 822 - 843
32-
871: {'bg': 'Хасково', 'en': 'Haskovo'}, # 844 - 871
33-
903: {'bg': 'Шумен', 'en': 'Shumen'}, # 872 - 903
34-
925: {'bg': 'Ямбол', 'en': 'Yambol'}, # 904 - 925
35-
999: {'bg': 'Друг', 'en': 'Other'}} # 926 - 999
36-
37-
38-
def generate(**kwargs):
39-
'''
40-
Method to generate numbers.
41-
'''
9+
# https://www.iso.org/obp/ui/#iso:code:3166:BG
10+
43: {'bg': 'Благоевград', 'en': 'Blagoevgrad', 'start': 0, 'iso': 'BG-01'}, # 000 - 043
11+
93: {'bg': 'Бургас', 'en': 'Burgas', 'start': 44, 'iso': 'BG-02'}, # 044 - 093
12+
139: {'bg': 'Варна', 'en': 'Varna', 'start': 94, 'iso': 'BG-03'}, # 094 - 139
13+
169: {'bg': 'Велико Търново', 'en': 'Veliko Turnovo', 'start': 140, 'iso': 'BG-04'}, # 140 - 169
14+
183: {'bg': 'Видин', 'en': 'Vidin', 'start': 170, 'iso': 'BG-05'}, # 170 - 183
15+
217: {'bg': 'Враца', 'en': 'Vratza', 'start': 184, 'iso': 'BG-06'}, # 184 - 217
16+
233: {'bg': 'Габрово', 'en': 'Gabrovo', 'start': 218, 'iso': 'BG-07'}, # 218 - 233
17+
281: {'bg': 'Кърджали', 'en': 'Kurdzhali', 'start': 234, 'iso': 'BG-09'}, # 234 - 281
18+
301: {'bg': 'Кюстендил', 'en': 'Kyustendil', 'start': 282, 'iso': 'BG-10'}, # 282 - 301
19+
319: {'bg': 'Ловеч', 'en': 'Lovech', 'start': 302, 'iso': 'BG-11'}, # 302 - 319
20+
341: {'bg': 'Монтана', 'en': 'Montana', 'start': 320, 'iso': 'BG-12'}, # 320 - 341
21+
377: {'bg': 'Пазарджик', 'en': 'Pazardzhik', 'start': 342, 'iso': 'BG-13'}, # 342 - 377
22+
395: {'bg': 'Перник', 'en': 'Pernik', 'start': 378, 'iso': 'BG-14'}, # 378 - 395
23+
435: {'bg': 'Плевен', 'en': 'Pleven', 'start': 396, 'iso': 'BG-15'}, # 396 - 435
24+
501: {'bg': 'Пловдив', 'en': 'Plovdiv', 'start': 436, 'iso': 'BG-16'}, # 436 - 501
25+
527: {'bg': 'Разград', 'en': 'Razgrad', 'start': 502, 'iso': 'BG-17'}, # 502 - 527
26+
555: {'bg': 'Русе', 'en': 'Ruse', 'start': 528, 'iso': 'BG-18'}, # 528 - 555
27+
575: {'bg': 'Силистра', 'en': 'Silistra', 'start': 556, 'iso': 'BG-19'}, # 556 - 575
28+
601: {'bg': 'Сливен', 'en': 'Sliven', 'start': 576, 'iso': 'BG-20'}, # 576 - 601
29+
623: {'bg': 'Смолян', 'en': 'Smolyan', 'start': 602, 'iso': 'BG-21'}, # 602 - 623
30+
721: {'bg': 'София', 'en': 'Sofia', 'start': 624, 'iso': 'BG-22'}, # 624 - 721
31+
751: {'bg': 'София (окръг)', 'en': 'Sofia (county)', 'start': 722, 'iso': 'BG-23'}, # 722 - 751
32+
789: {'bg': 'Стара Загора', 'en': 'Stara Zagora', 'start': 752, 'iso': 'BG-24'}, # 752 - 789
33+
821: {'bg': 'Добрич', 'en': 'Dobrich', 'start': 790, 'iso': 'BG-08'}, # 790 - 821
34+
843: {'bg': 'Търговище', 'en': 'Targovishte', 'start': 822, 'iso': 'BG-25'}, # 822 - 843
35+
871: {'bg': 'Хасково', 'en': 'Haskovo', 'start': 844, 'iso': 'BG-26'}, # 844 - 871
36+
903: {'bg': 'Шумен', 'en': 'Shumen', 'start': 872, 'iso': 'BG-27'}, # 872 - 903
37+
925: {'bg': 'Ямбол', 'en': 'Yambol', 'start': 904, 'iso': 'BG-28'}, # 904 - 925
38+
999: {'bg': 'Друг', 'en': 'Other', 'start': 926, 'iso': 'BG-XX'}} # 926 - 999
39+
40+
41+
def generate_random(gender=None,
42+
region=None,
43+
limit=10):
44+
"""
45+
Generate a random EGN.
46+
"""
47+
egns = []
48+
while len(egns) < limit:
49+
if region is None:
50+
rr = random.randint(1, 28)
51+
rand_region = f"BG-{rr:02d}"
52+
else:
53+
rand_region = region
54+
year = random.randint(1800, datetime.today().year - 1)
55+
month = random.randint(1, 12)
56+
day = random.randint(1, 28)
57+
date_from = f"{year}-{month}-{day}"
58+
date_to = datetime.today().strftime('%Y-%m-%d')
59+
rand_egn = generate(date_from=date_from, date_to=date_to, region=rand_region, gender=gender, limit=1)
60+
if len(rand_egn) == 1:
61+
egns.append(rand_egn[0])
62+
return egns
63+
64+
65+
def generate(date_from="1800-01-01",
66+
date_to=datetime.today().strftime('%Y-%m-%d'),
67+
gender=None,
68+
region=None,
69+
limit=10):
70+
"""
71+
Generate egn within boundaries.
72+
"""
73+
def get_region_range(region="Other"):
74+
"""
75+
Get region code.
76+
Region could be the latin, cyrrilic or ISO 3166 anotation of the region.
77+
"""
78+
start = 0
79+
end = 0
80+
for i in egn_regions:
81+
if region.lower() in [egn_regions[i]['en'].lower(),
82+
egn_regions[i]['bg'].lower(),
83+
egn_regions[i]['iso'].lower()]:
84+
start = egn_regions[i]['start']
85+
end = i
86+
return start, end
87+
88+
def get_dates_range(date_from, date_to, limit=None):
89+
"""
90+
Generate all dates between date range in ENG suitable format.
91+
Params in ISO 8601 format.
92+
"""
93+
dates = []
94+
try:
95+
sdate = datetime.strptime(date_from, '%Y-%m-%d')
96+
edate = datetime.strptime(date_to, '%Y-%m-%d')
97+
delta = edate - sdate
98+
99+
for i in range(delta.days + 1):
100+
if limit is not None and i > limit:
101+
return dates
102+
dday = str(sdate + timedelta(days=i))
103+
year, month, day = int(dday[0:4]), int(dday[6:7]), int(dday[8:10])
104+
if year >= 2000:
105+
month += 40
106+
year -= 2000
107+
elif year < 1900:
108+
month += 20
109+
year -= 1800
110+
else:
111+
# Gregorian calendar adoption: 31/03/1916 > +13 days > 14/04/1916
112+
if year == 1916 and month == 4 and day <= 13:
113+
continue
114+
year -= 1900
115+
dates.append(f"{year:02d}{month:02d}{day:02d}")
116+
return dates
117+
except Exception as e:
118+
print(e)
119+
return None
120+
121+
egns = []
122+
if not region:
123+
regions = range(0, 999, 1)
124+
else:
125+
region_start, region_end = get_region_range(region)
126+
regions = range(region_start, region_end, 1)
42127

43-
return '9941011142'
128+
if gender is not None:
129+
if gender[0].lower() == 'm': # Male
130+
regions = [x for x in regions if x % 2 == 0]
131+
else: # Female
132+
regions = [x for x in regions if x % 2]
133+
134+
regions = [f"{x:02d}" for x in regions]
135+
dates = get_dates_range(date_from, date_to, limit=limit)
136+
counter = 1
137+
138+
# Generate the list of egns
139+
for dt in dates:
140+
for region in regions:
141+
for check_num in range(0, 9, 1):
142+
egene = f"{dt}{region}{check_num}"
143+
if validate(egene):
144+
counter += 1
145+
egns.append(egene)
146+
if counter > limit:
147+
return egns
148+
return egns
44149

45150

46151
def get_date(egn):
@@ -51,7 +156,7 @@ def get_date(egn):
51156
from datetime import datetime
52157
try:
53158
year, month, day = int(egn[0:2]), int(egn[2:4]), int(egn[4:6])
54-
except:
159+
except Exception:
55160
return False
56161
if month >= 40:
57162
month -= 40
@@ -111,6 +216,7 @@ def parse(egn):
111216
'''
112217
Parse the EGN information and return hash with the values
113218
'''
219+
egn = str(egn)
114220
if not validate(egn):
115221
raise Exception('Invalid EGN')
116222
egn_hash = {}
@@ -125,12 +231,15 @@ def parse(egn):
125231
if key > region_num:
126232
egn_hash['region_bg'] = value['bg']
127233
egn_hash['region_en'] = value['en']
234+
egn_hash['region_iso'] = value['iso']
128235
break
129236

130237
# Parse the gender
131-
egn_hash['gender'] = 'male'
238+
egn_hash['gender'] = 'Male'
132239
if int(egn[8:9]) % 2:
133-
egn_hash['gender'] = 'female'
240+
egn_hash['gender'] = 'Female'
241+
242+
egn_hash['egn'] = egn
134243

135244
return egn_hash
136245

@@ -140,41 +249,39 @@ def parse_args(args):
140249
Method to parse the arguments to the program.
141250
'''
142251
parser = argparse.ArgumentParser()
143-
144-
parser.add_argument("-v", "--validate", help="Validate EGN",
145-
type=int)
146-
parser.add_argument("-p", "--parse", help="Parse EGN",
147-
type=int)
148-
# Generate
149-
parser.add_argument("-g", "--generate", help="Generate EGN",
150-
action="store_true")
151-
parser.add_argument("-s", "--sex", help="Sex/gender",
152-
type=str, choices=['male', 'female', 'm', 'f'],
153-
default=random.choice(['m', 'f']))
154-
parser.add_argument("-y", "--year", help="Year (between 1800-2099)",
155-
type=int, default=random.randint(1800, 2099))
156-
parser.add_argument("-m", "--month", help="Month (between 1-12)",
157-
type=int, default=random.randint(1, 12))
158-
parser.add_argument("-d", "--day", help="Day (between 1-31)",
159-
type=int, default=random.randint(1, 31))
160-
parser.add_argument("-r", "--region", help="Region (pernik/sofia etc.)",
161-
type=str, default=random.choice(['Pernik', 'Sofia']))
252+
today = format(datetime.today().strftime('%Y-%m-%d'))
253+
parser.add_argument("-v", "--validate", help="Validate EGN", type=str)
254+
parser.add_argument("-p", "--parse", help="Parse EGN", type=str)
255+
# Generation
256+
parser.add_argument("-l", "--limit", help="Limit of generated results (default: 1)", type=int, default=1)
257+
parser.add_argument("-r", "--random", help="Generate random EGN (default limit: 1)", action="store_true")
258+
parser.add_argument("-g", "--generate", help="Generate EGN", action="store_true")
259+
parser.add_argument("--gender", help="Gender - Male or Female", type=str, choices=['m', 'f'])
260+
parser.add_argument("--region", help="Region (Latin/Cyrrilic region name or ISO 3166 format)", type=str)
261+
parser.add_argument("--from", help="Date from generate (Y-m-d) (default: 1800-01-01)",
262+
type=str, default='1800-01-01')
263+
parser.add_argument("--to", type=str, help=f"Date to generate (Y-m-d) (default: {today})", default=today)
162264
return vars(parser.parse_args(args))
163265

164266

165267
def calc_args(parser):
166268
'''
167-
Method to parse the argparse values and return output
269+
Method to parse the argparse values and return an output.
168270
'''
169271

170272
if 'parse' in parser and parser['parse']:
171273
validate_egn = str(parser['parse'])
172274
if not validate(validate_egn):
173275
print("{} is invalid!".format(validate_egn))
174276
exit(1)
175-
print(parse(validate_egn))
277+
validate_egn = parse(validate_egn)
278+
validate_egn.pop('datetime')
279+
print(json.dumps(validate_egn))
280+
elif 'random' in parser and parser['random']:
281+
print("\n".join(generate_random(limit=parser['limit'], region=parser['region'], gender=parser['gender'])))
176282
elif 'generate' in parser and parser['generate']:
177-
print(generate())
283+
print("\n".join(generate(limit=parser['limit'], region=parser['region'], gender=parser['gender'],
284+
date_from=parser['from'], date_to=parser['to'])))
178285
elif 'validate' in parser and parser['validate']:
179286
validate_egn = str(parser['validate'])
180287
if validate(validate_egn):

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
setup(
1414
name='egn',
15-
version='0.1.3',
15+
version='0.1.4',
1616
packages=find_packages(exclude=['tests']),
1717
include_package_data=True,
1818
license='GPL-3.0', # example license

test/test_cli.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,16 @@ def test_cli_parse(capsys):
1717
egn_options = {'parse': 9941011142}
1818
egn.calc_args(egn_options)
1919
out, err = capsys.readouterr()
20-
assert ('Varna' in out and '2099' in out and 'male' in out)
20+
assert ('Varna' in out and '2099' in out and 'Male' in out)
2121

2222

2323
def test_cli_generate(capsys):
2424
''' Test generate via cli '''
25-
egn_options = {'generate': True}
25+
egn_options = {'generate': True, 'limit': 1, 'region': 'Sofia', 'gender': 'm',
26+
'from': '2020-01-01', 'to': '2020-12-12'}
2627
egn.calc_args(egn_options)
2728
out, err = capsys.readouterr()
28-
assert out == "9941011142\n"
29+
assert egn.validate(out.strip()) is True
2930

3031

3132
def test_cli_validation(capsys):

test/test_egn.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
# -*- coding: utf-8 -*-
22
import egn
3-
import sys
4-
import datetime
5-
import pytest
63

74

8-
def test_generation():
9-
''' Test generation produces valid egn '''
10-
assert egn.validate(egn.generate())
5+
def test_generation_options():
6+
''' Test generation produce valid egn with options '''
7+
for i in egn.generate(region='Pernik', gender='m', limit=100):
8+
assert egn.validate(i)
119

1210

13-
def test_generation_options():
11+
def test_generation_random():
1412
''' Test generation produce valid egn with options '''
15-
assert egn.validate(egn.generate(region=1, sex=1, day=1, month=1,
16-
year=2000, limit=100))
13+
random = egn.generate_random(limit=10)
14+
assert len(random) == 10
15+
for i in random:
16+
assert egn.validate(i)
1717

1818

1919
def test_valid():

0 commit comments

Comments
 (0)