Skip to content

Commit 8d9739c

Browse files
committed
fix: more assign script fixes, valid emails with DRF EmailField, notify_users=False
1 parent 3978df4 commit 8d9739c

File tree

5 files changed

+60
-36
lines changed

5 files changed

+60
-36
lines changed

scripts/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# boilerplate to treat as module

scripts/assignment_validation.py

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@
1414
"""
1515
import csv
1616
from collections import defaultdict, Counter
17-
from email.utils import parseaddr
18-
1917
import click
2018

19+
from utils import is_valid_email
20+
2121
INPUT_FIELDNAMES = ['university_name', 'email']
2222

2323

@@ -59,26 +59,29 @@ def print_plan_counts(input_file):
5959
print(plan, count)
6060

6161

62-
def is_valid_email(email):
63-
_, address = parseaddr(email)
64-
if not address:
65-
return False
66-
return True
67-
68-
6962
@click.command()
7063
@click.option(
7164
'--input-file',
7265
help='Path of local file containing email addresses to assign.',
7366
)
74-
def validate_emails(input_file):
67+
@click.option(
68+
'--output-file',
69+
help='Path of local file containing invalid email addresses store in a CSV.',
70+
)
71+
def validate_emails(input_file, output_file):
7572
invalid_emails = Counter()
76-
for row in _iterate_csv(input_file):
77-
if not is_valid_email(row['email']):
78-
invalid_emails[row['email']] += 1
79-
80-
print(f'There were {sum(invalid_emails.values())} invalid emails')
81-
print(invalid_emails)
73+
for index, row in enumerate(_iterate_csv(input_file)):
74+
email = row['email']
75+
uni_name = row['university_name']
76+
if not is_valid_email(email):
77+
invalid_emails[(uni_name, email)] += 1
78+
79+
print(f'There were a total of {sum(invalid_emails.values())} invalid emails for input of size {index + 1}')
80+
81+
with open(output_file, 'a+', encoding='latin-1') as f_out:
82+
writer = csv.writer(f_out, delimiter=',')
83+
for (uni_name, email), occurrences in invalid_emails.items():
84+
writer.writerow([uni_name, email, occurrences])
8285

8386

8487
@click.group()

scripts/local_assignment_multi.py

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,14 @@
4040
import csv
4141
import json
4242
import os
43-
import re
4443
import time
45-
from email.utils import parseaddr
4644
from pprint import pprint
4745

4846
import click
4947
import requests
5048

49+
from utils import is_valid_email
50+
5151

5252
DEFAULT_CHUNK_SIZE = 100
5353

@@ -66,7 +66,7 @@
6666
}
6767

6868
OUTPUT_FIELDNAMES = ['chunk_id', 'subscription_plan_uuid', 'email', 'license_uuid']
69-
INPUT_FIELDNAMES = ['email', 'university_name']
69+
INPUT_FIELDNAMES = ['university_name', 'email']
7070
PLANS_BY_NAME_FIELDNAMES = ['university_name', 'subscription_plan_uuid']
7171

7272

@@ -80,7 +80,6 @@ def _get_jwt(fetch_jwt=False, environment='local'):
8080
'client_secret': client_secret,
8181
'grant_type': 'client_credentials',
8282
'token_type': 'jwt',
83-
'scope': 'user_id email profile read write',
8483
}
8584
# we want to sent with a Content-Type of 'application/x-www-form-urlencoded'
8685
# so send in the `data` param instead of `json`.
@@ -101,7 +100,7 @@ def get_already_processed_emails(results_file):
101100
and returns a dictionary mapping already processed emails to their chunk_id.
102101
"""
103102
already_processed_emails = {}
104-
with open(results_file, 'a+') as f_in:
103+
with open(results_file, 'a+', encoding='latin-1') as f_in:
105104
f_in.seek(0)
106105
reader = csv.DictReader(f_in, fieldnames=OUTPUT_FIELDNAMES, delimiter=',')
107106

@@ -123,7 +122,7 @@ def get_already_processed_emails(results_file):
123122

124123
def get_plan_uuids_by_name(plans_by_name_file):
125124
plans_by_name = {}
126-
with open(plans_by_name_file, 'a+') as f_in:
125+
with open(plans_by_name_file, 'a+', encoding='latin-1') as f_in:
127126
f_in.seek(0)
128127
reader = csv.DictReader(f_in, fieldnames=PLANS_BY_NAME_FIELDNAMES, delimiter=',')
129128

@@ -148,11 +147,6 @@ def get_plan_uuids_by_name(plans_by_name_file):
148147
return plans_by_name
149148

150149

151-
def is_valid_email(email):
152-
_, address = parseaddr(email)
153-
return bool(address)
154-
155-
156150
def get_email_chunks(input_file_path, plans_by_name, chunk_size=DEFAULT_CHUNK_SIZE):
157151
"""
158152
Yield chunks of (chunk_id, subscription_plan, email) from the given input file.
@@ -182,7 +176,9 @@ def get_email_chunks(input_file_path, plans_by_name, chunk_size=DEFAULT_CHUNK_SI
182176
continue
183177

184178
university_name = row['university_name']
185-
subscription_plan_uuid = plans_by_name[university_name]
179+
subscription_plan_uuid = plans_by_name.get(university_name)
180+
if not subscription_plan_uuid:
181+
print(f'No plan matches the given name: {university_name}')
186182

187183
# This should only happen on the very first row we process
188184
if not current_subscription_plan_uuid:
@@ -210,7 +206,9 @@ def get_email_chunks(input_file_path, plans_by_name, chunk_size=DEFAULT_CHUNK_SI
210206
yield chunk_id, current_subscription_plan_uuid, current_chunk
211207

212208

213-
def _post_assignments(subscription_plan_uuid, emails_for_chunk, environment='local', fetch_jwt=False):
209+
def _post_assignments(
210+
subscription_plan_uuid, emails_for_chunk, environment='local', fetch_jwt=False, notify_users=False,
211+
):
214212
"""
215213
Make the POST request to assign licenses.
216214
"""
@@ -219,7 +217,7 @@ def _post_assignments(subscription_plan_uuid, emails_for_chunk, environment='loc
219217

220218
payload = {
221219
'user_emails': emails_for_chunk,
222-
'notify_users': False,
220+
'notify_users': notify_users,
223221
}
224222
headers = {
225223
"Authorization": "JWT {}".format(_get_jwt(fetch_jwt, environment=environment)),
@@ -273,7 +271,7 @@ def request_assignments(subscription_plan_uuid, chunk_id, emails_for_chunk, envi
273271

274272
def do_assignment_for_chunk(
275273
subscription_plan_uuid, chunk_id, email_chunk,
276-
already_processed, results_file, environment='local', fetch_jwt=False, sleep_interval=DEFAULT_SLEEP_INTERVAL
274+
already_processed, results_file, environment='local', fetch_jwt=False, sleep_interval=DEFAULT_SLEEP_INTERVAL,
277275
):
278276
"""
279277
Given a "chunk" list emails for which assignments should be requested, checks if the given
@@ -341,8 +339,13 @@ def do_assignment_for_chunk(
341339
help='Whether to fetch JWT based on stored client id and secret.',
342340
is_flag=True,
343341
)
342+
@click.option(
343+
'--dry-run',
344+
help='Just prints what emails would be assigned to plan if true.',
345+
is_flag=True,
346+
)
344347

345-
def run(input_file, plans_by_name_file, output_file, chunk_size, environment, sleep_interval, fetch_jwt):
348+
def run(input_file, plans_by_name_file, output_file, chunk_size, environment, sleep_interval, fetch_jwt, dry_run):
346349
"""
347350
Entry-point for this script.
348351
"""
@@ -353,10 +356,13 @@ def run(input_file, plans_by_name_file, output_file, chunk_size, environment, sl
353356
plan_uuids_by_name = get_plan_uuids_by_name(plans_by_name_file)
354357

355358
for chunk_id, subscription_plan_uuid, email_chunk in get_email_chunks(input_file, plan_uuids_by_name, chunk_size):
356-
do_assignment_for_chunk(
357-
subscription_plan_uuid, chunk_id, email_chunk,
358-
already_processed, output_file, environment, fetch_jwt, sleep_interval,
359-
)
359+
if dry_run:
360+
print(f'DRY RUN: chunk_id={chunk_id} would assign to plan {subscription_plan_uuid} emails: {email_chunk}')
361+
else:
362+
do_assignment_for_chunk(
363+
subscription_plan_uuid, chunk_id, email_chunk,
364+
already_processed, output_file, environment, fetch_jwt, sleep_interval,
365+
)
360366

361367
if __name__ == '__main__':
362368
run()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
click
22
requests
3+
django-rest-framework

scripts/utils.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# needed for email validation
2+
import django
3+
from rest_framework import serializers
4+
5+
EMAIL_FIELD = serializers.EmailField()
6+
7+
8+
def is_valid_email(email):
9+
try:
10+
EMAIL_FIELD.run_validators(email)
11+
return True
12+
except Exception:
13+
return False

0 commit comments

Comments
 (0)