Skip to content

Commit

Permalink
Merge pull request #200 from ECN/staging
Browse files Browse the repository at this point in the history
0.9.1 Merge
  • Loading branch information
campb303 authored Mar 16, 2021
2 parents 5b15bd2 + 60e754e commit 4ce6af7
Show file tree
Hide file tree
Showing 38 changed files with 1,412 additions and 376 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@ yarn-error.log*
/api/venv
__pycache__/
venv-manager.log
/api/.env
/api/.env
*.egg*
136 changes: 90 additions & 46 deletions api/ECNQueue.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,6 @@
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]
"""

#------------------------------------------------------------------------------#
Expand Down Expand Up @@ -62,7 +58,10 @@
#------------------------------------------------------------------------------#

def isValidItemName(name: str) -> bool:
"""Returns true if file name is a valid item name
"""Returns true if file name is a valid item name.
A file name is true if it contains between 1 and 3 integer numbers allowing for
any integer between 0 and 999.
Example:
isValidItemName("21") -> true
Expand All @@ -83,16 +82,23 @@ def isValidItemName(name: str) -> bool:
# Classes
#------------------------------------------------------------------------------#
class Item:
"""A single issue.
"""A chronological representation of an interaction with a user.
Example:
# Create an Item (ce100)
>>> item = Item("ce", 100)
>>> item = Item("ce", 100, headersOnly=false)
# Create an Item without parsing its contents (ce100)
>>> item = Item("ce", 100, headersOnly=True)
Args:
queue (str): The name of the Item's queue.
number (int): The number of the Item.
headersOnly (bool, optional): Whether or not to parse headers only. Defaults to True.
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.
content: A list of section dictionaries (only included if headersOnly is False).
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.
Expand All @@ -104,21 +110,22 @@ class Item:
department: The most recent department for this item.
dateReceived: The date this item was created.
jsonData: A JSON serializable representation of the Item.
Raises:
ValueError: When the number passed to the constructor cannot be parsed.
"""

def __init__(self, queue: str, number: int) -> None:
def __init__(self, queue: str, number: int, headersOnly: bool = False) -> None:
self.queue = queue
try:
self.number = int(number)
except ValueError:
raise ValueError(" Could not convert \"" +
number + "\" to an integer")

raise ValueError(f'Could not convert "{number}" to an integer')
self.__path = "/".join([queueDirectory, self.queue, str(self.number)])
self.lastUpdated = self.__getLastUpdated()
self.__rawItem = self.__getRawItem()
self.headers = self.__parseHeaders()
self.content = self.__parseSections()
if not headersOnly: self.content = self.__parseSections()
self.isLocked = self.__isLocked()
self.userEmail = self.__parseFromData(data="userEmail")
self.userName = self.__parseFromData(data="userName")
Expand All @@ -129,28 +136,12 @@ def __init__(self, queue: str, number: int) -> None:
self.priority = self.__getMostRecentHeaderByType("Priority")
self.department = self.__getMostRecentHeaderByType("Department")
self.building = self.__getMostRecentHeaderByType("Building")
self.dateReceived = self.__getFormattedDate(
self.__getMostRecentHeaderByType("Date"))
self.dateReceived = self.__getFormattedDate(self.__getMostRecentHeaderByType("Date"))
self.jsonData = {}

# TODO: Autopopulate jsonData w/ __dir__() command. Exclude `^_` and `jsonData`.
self.jsonData = {
"queue": self.queue,
"number": self.number,
"lastUpdated": self.lastUpdated,
"headers": self.headers,
"content": self.content,
"isLocked": self.isLocked,
"userEmail": self.userEmail,
"userName": self.userName,
"userAlias": self.userAlias,
"assignedTo": self.assignedTo,
"subject": self.subject,
"status": self.status,
"priority": self.priority,
"department": self.department,
"building": self.building,
"dateReceived": self.dateReceived
}
for attribute in self.__dir__():
if "_" not in attribute and attribute != "toJson" and attribute != "jsonData":
self.jsonData[attribute] = self.__getattribute__(attribute)

def __getLastUpdated(self) -> str:
"""Returns last modified time of item reported by the filesystem in mm-dd-yy hh:mm am/pm format.
Expand Down Expand Up @@ -212,7 +203,7 @@ def __parseHeaders(self) -> list:
# Example:
# [ce] QTime-Updated-By: campb303 becomes
# QTime-Updated-By: campb303
queuePrefixPattern = re.compile("\[.*\] {1}")
queuePrefixPattern = re.compile(r"\[.*?\] {1}")
for lineNumber in range(self.__getHeaderBoundary()):
line = self.__rawItem[lineNumber]
lineHasQueuePrefix = queuePrefixPattern.match(line)
Expand Down Expand Up @@ -263,6 +254,12 @@ def __parseSections(self) -> list:
for assignment in assignementLsit:
sections.append(assignment)

# Checks for empty content within an item and returns and
if contentEnd <= contentStart:
blankInitialMessage = self.__initialMessageParsing([""])
sections.append(blankInitialMessage)
return sections

# Checks for Directory Identifiers
if self.__rawItem[contentStart] == "\n" and self.__rawItem[contentStart + 1].startswith("\t"):

Expand Down Expand Up @@ -915,6 +912,10 @@ def __userReplyParsing(self, replyContent: list, lineNumber: int) -> dict:
if line == "\n":
newLineCounter = newLineCounter + 1

if newLineCounter == 2 and "datetime" not in replyFromInfo.keys():
errorMessage = "Expected \"Date: [datetime]\" in the header info"
return self.__errorParsing(line, lineNumber + lineNum + 1, errorMessage)

elif line == "===============================================\n":
endingDelimiterCount = endingDelimiterCount + 1

Expand Down Expand Up @@ -1190,7 +1191,19 @@ def __getUserAlias(self) -> str:
Returns:
str: User's Career Account alias if present or empty string
"""
emailUser, emailDomain = self.userEmail.split("@")


try:
emailUser, emailDomain = self.userEmail.split("@")

# Returns an error parse if the self.useremail doesn't contain exactally one "@" symbol
except ValueError:
# Parses through the self.headers list to find the "From" header and its line number
for lineNum, header in enumerate(self.headers):
if header["type"] == "From":
headerString = header["type"] + ": " + header["content"]
return self.__errorParsing(headerString, lineNum + 1, "Expected valid email Address")

return emailUser if emailDomain.endswith("purdue.edu") else ""

def __getFormattedDate(self, date: str) -> str:
Expand Down Expand Up @@ -1223,24 +1236,31 @@ 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:
"""A collection of items.
"""A collection of Items.
Example:
# Create a queue (ce)
>>> queue = Queue("ce")
# Create a queue without parsing item contents (ce)
>>> queue = Queue("ce", headersOnly=False)
Args:
queue (str): The name of the queue.
headersOnly (bool, optional): Whether or not to parse headers only. Defaults to True.
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:
def __init__(self, name: str, headersOnly: bool = True) -> None:
self.name = name
self.headersOnly = headersOnly
self.__directory = queueDirectory + "/" + self.name + "/"
self.items = self.__getItems()
self._index = 0

self.jsonData = {
"name": self.name,
Expand All @@ -1261,7 +1281,7 @@ def __getItems(self) -> list:
isFile = True if os.path.isfile(itemPath) else False

if isFile and isValidItemName(item):
items.append(Item(self.name, item))
items.append(Item(self.name, item, headersOnly=self.headersOnly))

return items

Expand All @@ -1287,6 +1307,13 @@ def __len__(self) -> int:
def __repr__(self) -> str:
return f'{self.name}_queue'

# Implements the interable interface requirements by passing direct references
# to the item list's interable values.
def __iter__(self) -> list:
return iter(self.items)
def __next__(self) -> int:
return iter(self.items).__next__()

def getValidQueues() -> list:
"""Returns a list of queues on the filesystem excluding ignored queues.
Expand Down Expand Up @@ -1329,20 +1356,37 @@ def getQueueCounts() -> list:
queueInfo = []
for queue in getValidQueues():
possibleItems = os.listdir(queueDirectory + "/" + queue)
validItems = [isValidItemName for file in possibleItems]
validItems = [file for file in possibleItems if isValidItemName(file)]
queueInfo.append( {"name": queue, "number_of_items": len(validItems)} )
return queueInfo


# Sorts list of queue info alphabetically
sortedQueueInfo = sorted(queueInfo, key = lambda queueInfoList: queueInfoList['name'])

def loadQueues() -> list:
return sortedQueueInfo

def loadAllQueues(headersOnly: bool = True) -> list:
"""Return a list of Queues for each queue.
Example:
# Load all Queues without parsing Item content
>>> loadAllQueues();
Load all Queues and parsing Item content
>>> loadAllQueues(headersOnly=False)
Args:
headersOnly (bool, optional): Whether or not to parse headers only. Defaults to True.
Returns:
list: list of Queues for each queue.
list: List of Queues for each queue.
"""
queues = []

for queue in getValidQueues():
queues.append(Queue(queue))
queues.append(Queue(queue, headersOnly=headersOnly))

return queues

if __name__ == "__main__":
abe = Queue("abe")
abeCount = getQueueCounts()
print()
Loading

0 comments on commit 4ce6af7

Please sign in to comment.