Skip to content

Scruba a dub dub for ecn queue #103

Merged
merged 13 commits into from
Oct 29, 2020
121 changes: 73 additions & 48 deletions api/ECNQueue.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,32 @@
# TODO: Add ECNQueue module documentation
"""A library for interacting with Purdue ECN's ticketing system.
This library allows interacting with queue Items (called Items) and collections
of items (called Queues).
Example:
# Create a single Item (ce100)
>>> item = Item("ce", 100)
# Get the sender's email address from an Item
>>> item = Item("ce", 100)
>>> item.userEmail
# Create an entire Queue (ce)
>>> queue = Queue("ce")
# Get the number of items in a Queue
>>> queue = Queue("ce")
>>> numItems = len(queue)
# Get all queues (and their items)
>>> queues = getQueues()
Attributes:
queueDirectory: The directory to load queues from.
queuesToIgnore: Queues that will not be loaded when running getQueues()
Raises:
# TODO: Add description(s) of when a ValueError is raised.
ValueError: [description]
"""

#------------------------------------------------------------------------------#
# Imports
Expand All @@ -20,31 +48,45 @@
currentFileDirectory = os.path.dirname(currentFilePath)
currentFileDirectoryParent = os.path.dirname(currentFileDirectory)
queueDirectory = os.path.join(currentFileDirectoryParent, "q-snapshot")
#queueDirectory = "/usr/site/uds/qcopy/11"

# Queues to not load in getQueues()
queuesToIgnore = ["archives", "drafts", "inbox"]

# B/c some items don't have a From field
# See coral259
queuesToIgnore.append("coral")
queuesToIgnore = ["archives", "drafts", "inbox", "coral"]



#------------------------------------------------------------------------------#
# Classes
#------------------------------------------------------------------------------#
class Item:
# TODO: Add Item class documentation
"""A single issue.
Example:
# Create an Item (ce100)
>>> item = Item("ce", 100)
Attributes:
lastUpdated: An ISO 8601 formatted time string showing the last time the file was updated according to the filesystem.
headers: A list of dictionaries containing header keys and values.
content: A list of section dictionaries.
isLocked: A boolean showing whether or not a lockfile for the item is present.
userEmail: The email address of the person who this item is from.
userName: The real name of the person who this item is from.
userAlias: The Purdue career account alias of the person this item is from.
assignedTo: The Purdue career account alias of the person this item is assigned to
subject: The subject of the original message for this item.
status: The most recent status update for the item.
priority: The most recent priority for this item.
department: The most recent department for this item.
dateReceived: The date this item was created.
jsonData: A JSON serializable representation of the Item.
"""

def __init__(self, queue: str, number: int) -> None:

self.queue = queue
try:
self.number = int(number)
except ValueError:
raise ValueError("Could not convert \"" + number + "\" to an integer")
#self.number = number
raise ValueError(" Could not convert \"" + number + "\" to an integer")

self.__path = "/".join([queueDirectory, self.queue, str(self.number)])
self.lastUpdated = self.__getLastUpdated()
Expand All @@ -55,14 +97,15 @@ def __init__(self, queue: str, number: int) -> None:
self.userEmail = self.__parseFromData(data="userEmail")
self.userName = self.__parseFromData(data="userName")
self.userAlias = self.__getUserAlias()
self.assignedTo = self.__getAssignedTo()
self.assignedTo = self.__getMostRecentHeaderByType("Assigned-To")
self.subject = self.__getMostRecentHeaderByType("Subject")
self.status = self.__getMostRecentHeaderByType("Status")
self.priority = self.__getMostRecentHeaderByType("Priority")
self.department = self.__getMostRecentHeaderByType("Department")
self.building = self.__getMostRecentHeaderByType("Building")
self.dateReceived = self.__getFormattedDate(self.__getMostRecentHeaderByType("Date"))

# TODO: Autopopulate jsonData w/ __dir__() command. Exclude `^_` and `jsonData`.
self.jsonData = {
"queue": self.queue,
"number": self.number,
Expand Down Expand Up @@ -91,6 +134,7 @@ def __getLastUpdated(self) -> str:
Returns:
str: last modified time of item reported by the filesystem in mm-dd-yy hh:mm am/pm format.
"""
# TODO: Simplify this code block by allowing __getFormattedDate to accept milliseconds since the epoch.
unixTime = os.path.getmtime(self.__path)
formattedTime = time.strftime('%m-%d-%y %I:%M %p', time.localtime(unixTime))
return self.__getFormattedDate(formattedTime)
Expand Down Expand Up @@ -151,7 +195,8 @@ def __parseHeaders(self) -> list:

headerString += line

message = email.message_from_string(headerString + "".join(self.__getContent()))
# message = email.message_from_string(headerString + "".join(self.__getContent()))
message = email.message_from_string(headerString)

headers = []
for key in message.keys():
Expand All @@ -161,26 +206,6 @@ def __parseHeaders(self) -> list:

# TODO: Implement attachment parsing

def __getContent(self) -> list:
"""Returns a dictionary of lines of the item body.
Example:
"Hello. I need help.\\n\\n*** Status updated by: campb303 at: 6/23/2020 13:26:55 ***\\nDont Delete" becomes
[
"Hello. I need help.\\n",
"\\n",
"*** Status updated by: campb303 at: 6/23/2020 13:26:55 ***\\n",
"Don't Delete",
]
Returns:
list: Lines of the body item.
"""
contentStart = self.__getHeaderBoundary() + 1
contentEnd = len(self.__rawItem) - 1
return self.__rawItem[ contentStart : contentEnd ]


def __parseSections(self) -> list:
# List of all item events
sections = []
Expand Down Expand Up @@ -1008,7 +1033,6 @@ def __getSortedSections(self, sectionsList: list) -> list:

return sortedSections


def __isLocked(self) -> Union[str, bool]:
"""Returns a string info about the lock if true and a bool False if false
Expand Down Expand Up @@ -1089,16 +1113,6 @@ def __getUserAlias(self) -> str:
"""
emailUser, emailDomain = self.userEmail.split("@")
return emailUser if emailDomain.endswith("purdue.edu") else ""

def __getAssignedTo(self) -> str:
"""Returns the alias of the person this item was most recently assigned to.
Returns empty string if this item isn't assigned.
Returns:
str: Alias of the person item is assigned to or empty string.
"""
assignedTo = self.__getMostRecentHeaderByType("Assigned-To")
return assignedTo

def __getFormattedDate(self, date: str) -> str:
"""Returns the date/time formatted as RFC 8601 YYYY-MM-DDTHH:MM:SS+00:00.
Expand All @@ -1109,8 +1123,8 @@ def __getFormattedDate(self, date: str) -> str:
str: Properly formatted date/time recieved or empty string.
"""
try:
parsedDate = parse(date, default= datetime.datetime(2017, 10, 13, tzinfo=tz.gettz('EDT')))
#parsedDate = parse(date, default= datetime.datetime(2017, 10, 13, tzinfo=datetime.timezone.)
# This date is never meant to be used. The default attribute is just to set timezone.
parsedDate = parse(date, default=datetime.datetime(1970, 0, 1, tzinfo=tz.gettz('EDT')))
except:
return ""

Expand All @@ -1129,8 +1143,19 @@ def toJson(self) -> dict:
def __repr__(self) -> str:
return self.queue + str(self.number)

# TODO: Make Queue iterable using __iter__. See: https://thispointer.com/python-how-to-make-a-class-iterable-create-iterator-class-for-it/
class Queue:
# TODO: Add Queue class documentation
"""A collection of items.
Example:
# Create a queue (ce)
>>> queue = Queue("ce")
Attributes:
name: The name of the queue.
items: A list of Items in the queue.
jsonData: A JSON serializable representation of the Queue.
"""

def __init__(self, name: str) -> None:
self.name = name
Expand Down Expand Up @@ -1198,4 +1223,4 @@ def getQueues() -> list:
if isDirectory and isValid:
queues.append(Queue(file))

return queues
return queues