Browse Source

Added ability to do multiple weeks in advance

master
Roxie Gibson 4 years ago
parent
commit
36575c1221
No known key found for this signature in database
7 changed files with 89 additions and 30 deletions
  1. +4
    -1
      .vscode/settings.json
  2. +0
    -0
      changelog.md
  3. +37
    -2
      readme.md
  4. +1
    -1
      setup.py
  5. +28
    -13
      supper/__main__.py
  6. +12
    -8
      supper/api.py
  7. +7
    -5
      supper/dates.py

+ 4
- 1
.vscode/settings.json View File

@@ -15,11 +15,14 @@
"vdbd"
],
"cSpell.words": [
"Org's",
"caat",
"organisers",
"outofoffice",
"pattern",
"seatingplan",
"seatplangen",
"strftime"
]
],
"python.linting.pylintEnabled": true
}

+ 0
- 0
changelog.md View File


+ 37
- 2
readme.md View File

@@ -46,8 +46,6 @@ offline_access

## Installation



Once the app has been created, git clone this repo, cd into its folder and install it into your user's Python PATH.

```sh
@@ -128,6 +126,43 @@ supper -c ~/.config/supper.yaml -o "Seating Plan {:%Y-%m-%d}.csv"

This will output a file called `Seating Plan 2019-09-12.csv`

### Multiple Weeks

The script can output multiple weeks in advance. You can provide a number of weeks in advance with the -w or --weeks flag.

```sh
supper -w 2 # Creates three csv's. This weeks, and two weeks in advance.
```

If datetime formatting is provided for the filename, it will give the correct datetime for that files week. Otherwise "_x" will be provided to make sure the script doesn't overwrite itself.

#### Examples

```sh
supper -o "Seating Plan {:%Y-%m-%d}.csv" -w 2
```

Will create 3 files named

```
Seating Plan 2019-10-21.csv
Seating Plan 2019-10-28.csv
Seating Plan 2019-11-04.csv
```
---

```sh
supper -o "Seating Plan.csv" -w 2
```

Will create 3 files named

```
Seating Plan.csv
Seating Plan_1.csv
Seating Plan_2.csv
```

### Debug

You can enable debug output using the `-d` or `--debug` flags

+ 1
- 1
setup.py View File

@@ -34,6 +34,6 @@ setuptools.setup(
entry_points={"console_scripts": ["supper=supper.__main__:main"]},
python_requires=">=3.5",
install_requires=("o365==2.0.1", "pyyaml==5.1.1"),
version="1.0",
version="1.1.0",
license="GPL-3"
)

+ 28
- 13
supper/__main__.py View File

@@ -48,10 +48,10 @@ parser.add_argument(
)
parser.add_argument("-d", "--debug", action="store_true", help="Enable debug output")

# TODO: Add new argument, n amount of future weeks. Then pass this onto the request which handles it
parser.add_argument("-w", "--weeks", type=int, default=0, dest="weeks_extra", help="Number of weeks to add extra to the current week")


def read_config_file(config_path):
def read_config_file(config_path: str):
"""
Reads config file and sets up variables foo

@@ -62,7 +62,7 @@ def read_config_file(config_path):
return config


def format_output_path(output_path):
def format_output_path(output_path: str, date: datetime):
"""
Checks the string for datetime formatting and formats it if possible.

@@ -70,7 +70,7 @@ def format_output_path(output_path):
:return: str of the new output path
"""
try:
new_path = output_path.format(datetime.now())
new_path = output_path.format(date)
if new_path.split(".")[-1] != "csv":
new_path += ".csv"
LOG.info("Output path does NOT have '.csv' file extension. Adding '.csv' to end of output_path.")
@@ -97,15 +97,15 @@ def parse_args():
LOG.setLevel(logging.WARNING)
HANDLER.setLevel(logging.WARNING)

output_path = format_output_path(args.output_path)

if args.config_path:
# Read the file provided and return the required config
try:
config = read_config_file(args.config_path)
config["weeks_extra"] = args.weeks_extra
config["config_path"] = args.config_path
config["output_path"] = output_path
config["users"] = sorted([x.lower() for x in config["users"]]) # make all names lowercase and sort alphabetically
config["output_path"] = args.output_path # Needs formatting
# make all names lowercase and sort alphabetically
config["users"] = sorted([x.lower() for x in config["users"]])
LOG.debug("Loaded config successfully from '%s'", args.config_path)
return config
except FileNotFoundError:
@@ -114,7 +114,7 @@ def parse_args():
exit(1)
except (yaml.parser.ParserError, TypeError):
# Cannot parse opened file
# TypeError is sometimes raised if the metadata of the file is correct but content doesn't parse
# TypeError is raised if the metadata of the file is correct but content doesn't parse
LOG.error("Cannot parse config file. Make sure the provided config is a YAML file and that is is formatted correctly. Exiting...")
exit(1)
else:
@@ -167,10 +167,25 @@ def main():

LOG.debug("Session created and authenticated. %s", session)

ooo = session.get_ooo_list(email)
create_ooo_csv(ooo, users, output_path)
monday, friday = dates.get_week_datetime()
print("\nCreated CSV seating plan for week {:%a %d/%m/%Y} to {:%a %d/%m/%Y} at {}".format(monday, friday, abspath(output_path)))
filenames = []
for i in range(config["weeks_extra"] + 1):
ooo = session.get_ooo_list(email, i)
monday, friday = dates.get_week_datetime(i)
output_filename = format_output_path(output_path, monday)
# Check if filename is the same
if output_filename in filenames:
# Add number to end of filename to avoid overwrites
path, ext = output_filename.split(".")
output_filename = ".".join([path + f"_{i}", ext])
filenames.append(output_filename)

create_ooo_csv(ooo, users, output_filename)

print(
"Created CSV seating plan for week {:%a %d/%m/%Y} to {:%a %d/%m/%Y} at {}".format(
monday, friday, abspath(output_filename)
)
)


if __name__ == "__main__":

+ 12
- 8
supper/api.py View File

@@ -26,6 +26,7 @@ from . import ACCESS_TOKEN, STRFTIME, STRPTIME, LOG, dates

class Account(O365.Account):
"""Wrapper for the O365 Account class to add our api interactions"""

@classmethod
def create_session(cls, credentials, tenant_id):
"""
@@ -75,10 +76,9 @@ class Account(O365.Account):

def get_event_range(self, beginning_of_week: datetime, email: str):
"""
Makes api call to grab a calender view within a 2 week window either side of the current week.
Makes call to grab calender view within a 2 week window either side of the current week.

:param beginning_of_week: datetime object for this weeks monday
:param connection: a connection to the office365 api
:return: dict of json response
"""
base_url = "https://outlook.office.com/api/v2.0/"
@@ -97,15 +97,15 @@ class Account(O365.Account):
resp = self.con.oauth_request(url, "get")
return resp.json()

def get_ooo_list(self, email: str):
def get_ooo_list(self, email: str, week_no: int):
"""
Makes request and parses data into a list of users who will not be in the office

:param email: string of the outofoffice email where the out of office calender is located
:param connection: a connection to the office365 api
:param week_no: week number. 1 = Current week, n > 1 = current week + n weeks
:return: list of 5 lists representing 5 days. Each contains lowercase names of who is not in the office.
"""
monday, friday = dates.get_week_datetime()
monday, friday = dates.get_week_datetime(week_no)
try:
events = self.get_event_range(monday, email)
LOG.debug("Received response for two week range from week beginning with {:%Y-%m-%d} from outofoffice account with email: {}".format(monday, email))
@@ -117,7 +117,8 @@ class Account(O365.Account):
outofoffice = [[], [], [], [], []]

for event in events:
# removes last char due to microsoft's datetime using 7 sigfigs for microseconds, python uses 6
# removes last char due to microsoft's datetime using 7 digits
# for microseconds, python uses 6
start = datetime.strptime(event["Start"]["DateTime"][:-1], STRPTIME)
end = datetime.strptime(event["End"]["DateTime"][:-1], STRPTIME)
attendees = event["Attendees"]
@@ -126,7 +127,7 @@ class Account(O365.Account):
organizer = event["Organizer"]

if not attendees and organizer["EmailAddress"]["Address"] != email:
# Sometimes user will be the one who makes the event, not the outofoffice account. Get the organizer.
# Sometimes user will be the one who makes the event. Get the organizer.
attendees = [event["Organizer"]]

if (end - start) <= timedelta(days=1):
@@ -135,7 +136,10 @@ class Account(O365.Account):
# Event is within the week we are looking at, add all attendees
weekday = outofoffice[start.weekday()]
if not attendees:
LOG.warning("Event '%s' has no attendees. Cannot add to outofoffice list.", event["Subject"])
LOG.warning(
"Event '%s' has no attendees. Cannot add to outofoffice list.",
event["Subject"]
)
weekday = self.add_attendees_to_ooo_list(attendees, weekday)
else:
# Check if long events cover the days of this week

+ 7
- 5
supper/dates.py View File

@@ -16,7 +16,9 @@

from datetime import datetime, timedelta

def get_week_datetime(start: datetime = None):
from . import LOG

def get_week_datetime(start: int = 0):
"""
Gets the current week's Monday and Friday to be used to filter a calendar.

@@ -26,10 +28,10 @@ def get_week_datetime(start: datetime = None):
:param start: Specifies the today variable, used for make future weeks if given.
:return: Monday and Friday: Datetime objects
"""
if start:
today = start
else:
today = datetime.now()
today = datetime.now() + timedelta(weeks=start)
if start > 5:
LOG.warning("Extra weeks exceeds 5, script may run slowly.")

weekday = today.weekday()
extra_time = timedelta(

Loading…
Cancel
Save