<commit_before># -*- coding: utf-8 -*-

import urllib2, sqlite3, hashlib, re, random
import html_parser
import url_util, compress_util
import gzip
import cStringIO
import socket
import random

def random_ip():
    return '.'.join([str(random.randint(23, 179)) for i in range(4)])

def download(url, timeout):
    headers = {'Referer': url, \
            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36',\
            'X-Forwarded-For' : random_ip()}
    try:
        request = urllib2.Request(url = url, headers = headers)
        response = urllib2.urlopen(request, timeout = timeout)
        return (200, response.read())
    except urllib2.HTTPError, e:
        return_code = e.getcode()
    except urllib2.URLError, e:
        return_code = -1
    except socket.timeout:
        return_code = -2
    return (return_code, None)

def get_url_hash(url):
    return int(hashlib.sha224(url).hexdigest(), 16) % (1 << 63)

class Crawler:
    def __init__(self, config, db_name, shard, total):
        self.db_root = db_name + '.' + str(total)
        self.db_name = db_name + '.' + str(total) + '.' + str(shard)
        self.total = total
        self.shard = shard
        self.init_crawler_db()
        self.prev_getaddrinfo = socket.getaddrinfo
        self.dns_cache = {}

        socket.getaddrinfo = self.getaddrinfo

        source = ''
        seed_urls = []
        updates = []
        oneoffs = []
        self.sources = []
        for line in file(config):
            line = line.strip()
            if len(line) == 0:
                continue
            if line[0] == '#':
                continue
            if line[0] == '[':
                source = line
            elif line.find('=') > 0:
                k, v = line.split('=', 1)
                k = k.strip()
                v = v.strip()
                if k == 'update':
                    updates.append(v)
                elif k == 'oneoff':
                    oneoffs.append(v)
                elif k == 'url':
                    seed_urls.append(v)
            elif len(line) == 0:
                if source != '':
                    self.sources.append((source, seed_urls, updates, oneoffs))
                source = ''
                seed_urls = []
                updates= []
                oneoffs = []
        if source != '':
            self.sources.append((source, seed_urls, updates, oneoffs))

    def getaddrinfo(self, *args):
        if args in self.dns_cache:
            return self.dns_cache[args]
        else:
            res = self.prev_getaddrinfo(*args)
            self.dns_cache[args] = res
            return res

    def get_crawled_urls(self):
        self.cursor.execute('select src_id from link_graph')
        ret = set()
        for uid, in self.cursor.fetchall():
            ret.add(uid)
        return ret

    def get_new_urls(self):
        ret = set()

        for i in range(self.total):
            db = sqlite3.connect(self.db_root + '.' + str(i))
            db.text_factory = lambda x: unicode(x, 'utf-8', 'ignore')
            cursor = db.cursor()
            cursor.execute('select id, url from urls')
            for uid, url in cursor.fetchall():
                if uid % self.total == self.shard:
                    ret.add(url)
        return ret

    def init_crawler_db(self):
        self.db = sqlite3.connect(self.db_name)
        self.cursor = self.db.cursor()
        self.cursor.execute('create table if not exists urls (id bigint not null, url varchar(255) not null, html blob, updated_at timestamp default current_timestamp, primary key (id))')
        self.db.commit()

        self.cursor.execute('create table if not exists link_graph (src_id bigint not null, dst_id bigint not null, anchor_text varchar(255) not null, updated_at timestamp default current_timestamp, primary key (src_id, dst_id))')
        self.db.commit()

        self.db.text_factory = lambda x: unicode(x, 'utf-8', 'ignore')

    def insert_url(self, url, html):
        url_id = get_url_hash(url)
        if html != None:
            if len(html) < 1500000:
                self.cursor.execute('replace into urls (id, url, html) values (?, ?, ?)', (url_id, url, sqlite3.Binary(compress_util.compress(html))))
        else:
            self.cursor.execute('insert or ignore into urls (id, url) values (?, ?)', (url_id, url))
        self.db.commit()

    def insert_to_link_graph(self, src_url, dst_url, anchor_text):
        src_id = get_url_hash(src_url)
        dst_id = get_url_hash(dst_url)
        self.cursor.execute('replace into link_graph (src_id, dst_id, anchor_text) values (?, ?, ?)', (src_id, dst_id, anchor_text))
        self.db.commit()

    def print_urls(self, limit = 10):
        self.cursor.execute('select * from urls limit ' + str(limit))
        for e in self.cursor.fetchall():
            print e

    def match_single_pattern(self, url, pattern):
        match = re.match(pattern, url)
        if match and match.group(0) == url:
            return True
        else:
            return False

    def match_patterns(self, url, patterns):
        for pattern in patterns:
            if self.match_single_pattern(url, pattern):
                return True
        return False

    def encode_unicode(self, buf, charset):
        try:
            ret = buf.decode(charset)
            return ret
        except Exception, e:
            return ''

    def crawl_source(self, seed_urls, update_patterns, oneoff_patterns, limit = 500):
        crawl_queue = [x for x in seed_urls if self.match_patterns(x, update_patterns)]
        crawl_queue += [x for x in self.get_new_urls()]
        visited = self.get_crawled_urls()
        crawled = 0
        queue_urls = []
        while len(crawl_queue) > 0 and crawled < limit:
            try:
                url = crawl_queue.pop(0)
                url_id = get_url_hash(url)
                if url_id % self.total != self.shard:
                    continue
                if url_id in visited:
                    continue
                if self.match_patterns(url, update_patterns) == False:
                    continue
                code, html = download(url, timeout = 5)
                if html == None:
                    continue
                self.insert_url(url, html)
                visited.add(url_id)
                crawled += 1
                if crawled % 10 == 0:
                    print crawled, url
                doc = html_parser.HTMLParser(html)
                for sub_url, anchor_text in doc.links():
                    sub_url = sub_url.strip(' \'')
                    sub_url = url_util.combine_url(url, sub_url)
                    anchor_text = self.encode_unicode(anchor_text, doc.charset())
                    if self.match_patterns(sub_url, update_patterns) or self.match_patterns(sub_url, oneoff_patterns):
                        self.insert_to_link_graph(url, sub_url, anchor_text)
                        self.insert_url(sub_url, None)
                        crawl_queue.append(sub_url)
            except Exception, e:
                continue

    def crawl(self):
        for source, seed_urls, updates, oneoffs in self.sources:
            self.crawl_source(seed_urls, updates, oneoffs)
<commit_msg>add more debug info<commit_after># -*- coding: utf-8 -*-

import urllib2, sqlite3, hashlib, re, random
import html_parser
import url_util, compress_util
import gzip
import cStringIO
import socket
import random

def random_ip():
    return '.'.join([str(random.randint(23, 179)) for i in range(4)])

def download(url, timeout):
    headers = {'Referer': url, \
            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36',\
            'X-Forwarded-For' : random_ip()}
    try:
        request = urllib2.Request(url = url, headers = headers)
        response = urllib2.urlopen(request, timeout = timeout)
        return (200, response.read())
    except urllib2.HTTPError, e:
        return_code = e.getcode()
    except urllib2.URLError, e:
        return_code = -1
    except socket.timeout:
        return_code = -2
    return (return_code, None)

def get_url_hash(url):
    return int(hashlib.sha224(url).hexdigest(), 16) % (1 << 63)

class Crawler:
    def __init__(self, config, db_name, shard, total):
        self.db_root = db_name + '.' + str(total)
        self.db_name = db_name + '.' + str(total) + '.' + str(shard)
        self.total = total
        self.shard = shard
        self.init_crawler_db()
        self.prev_getaddrinfo = socket.getaddrinfo
        self.dns_cache = {}

        socket.getaddrinfo = self.getaddrinfo

        source = ''
        seed_urls = []
        updates = []
        oneoffs = []
        self.sources = []
        for line in file(config):
            line = line.strip()
            if len(line) == 0:
                continue
            if line[0] == '#':
                continue
            if line[0] == '[':
                source = line
            elif line.find('=') > 0:
                k, v = line.split('=', 1)
                k = k.strip()
                v = v.strip()
                if k == 'update':
                    updates.append(v)
                elif k == 'oneoff':
                    oneoffs.append(v)
                elif k == 'url':
                    seed_urls.append(v)
            elif len(line) == 0:
                if source != '':
                    self.sources.append((source, seed_urls, updates, oneoffs))
                source = ''
                seed_urls = []
                updates= []
                oneoffs = []
        if source != '':
            self.sources.append((source, seed_urls, updates, oneoffs))

    def getaddrinfo(self, *args):
        if args in self.dns_cache:
            return self.dns_cache[args]
        else:
            res = self.prev_getaddrinfo(*args)
            self.dns_cache[args] = res
            return res

    def get_crawled_urls(self):
        self.cursor.execute('select src_id from link_graph')
        ret = set()
        for uid, in self.cursor.fetchall():
            ret.add(uid)
        return ret

    def get_new_urls(self):
        ret = set()

        for i in range(self.total):
            db = sqlite3.connect(self.db_root + '.' + str(i))
            db.text_factory = lambda x: unicode(x, 'utf-8', 'ignore')
            cursor = db.cursor()
            cursor.execute('select id, url from urls')
            for uid, url in cursor.fetchall():
                if uid % self.total == self.shard:
                    ret.add(url)
        return ret

    def init_crawler_db(self):
        self.db = sqlite3.connect(self.db_name)
        self.cursor = self.db.cursor()
        self.cursor.execute('create table if not exists urls (id bigint not null, url varchar(255) not null, html blob, updated_at timestamp default current_timestamp, primary key (id))')
        self.db.commit()

        self.cursor.execute('create table if not exists link_graph (src_id bigint not null, dst_id bigint not null, anchor_text varchar(255) not null, updated_at timestamp default current_timestamp, primary key (src_id, dst_id))')
        self.db.commit()

        self.db.text_factory = lambda x: unicode(x, 'utf-8', 'ignore')

    def insert_url(self, url, html):
        url_id = get_url_hash(url)
        if html != None:
            if len(html) < 1500000:
                self.cursor.execute('replace into urls (id, url, html) values (?, ?, ?)', (url_id, url, sqlite3.Binary(compress_util.compress(html))))
        else:
            self.cursor.execute('insert or ignore into urls (id, url) values (?, ?)', (url_id, url))
        self.db.commit()

    def insert_to_link_graph(self, src_url, dst_url, anchor_text):
        src_id = get_url_hash(src_url)
        dst_id = get_url_hash(dst_url)
        self.cursor.execute('replace into link_graph (src_id, dst_id, anchor_text) values (?, ?, ?)', (src_id, dst_id, anchor_text))
        self.db.commit()

    def print_urls(self, limit = 10):
        self.cursor.execute('select * from urls limit ' + str(limit))
        for e in self.cursor.fetchall():
            print e

    def match_single_pattern(self, url, pattern):
        match = re.match(pattern, url)
        if match and match.group(0) == url:
            return True
        else:
            return False

    def match_patterns(self, url, patterns):
        for pattern in patterns:
            if self.match_single_pattern(url, pattern):
                return True
        return False

    def encode_unicode(self, buf, charset):
        try:
            ret = buf.decode(charset)
            return ret
        except Exception, e:
            return ''

    def crawl_source(self, seed_urls, update_patterns, oneoff_patterns, limit = 500):
        crawl_queue = [x for x in seed_urls if self.match_patterns(x, update_patterns)]
        crawl_queue += [x for x in self.get_new_urls()]
        visited = self.get_crawled_urls()
        crawled = 0
        queue_urls = []
        while len(crawl_queue) > 0 and crawled < limit:
            try:
                url = crawl_queue.pop(0)
                url_id = get_url_hash(url)
                if url_id % self.total != self.shard:
                    continue
                if url_id in visited:
                    continue
                if self.match_patterns(url, update_patterns) == False:
                    continue
                code, html = download(url, timeout = 5)
                if html == None:
                    continue
                self.insert_url(url, html)
                visited.add(url_id)
                crawled += 1
                print crawled, len(crawl_queue), url
                doc = html_parser.HTMLParser(html)
                for sub_url, anchor_text in doc.links():
                    sub_url = sub_url.strip(' \'')
                    sub_url = url_util.combine_url(url, sub_url)
                    anchor_text = self.encode_unicode(anchor_text, doc.charset())
                    if self.match_patterns(sub_url, update_patterns) or self.match_patterns(sub_url, oneoff_patterns):
                        self.insert_to_link_graph(url, sub_url, anchor_text)
                        self.insert_url(sub_url, None)
                        crawl_queue.append(sub_url)
            except Exception, e:
                continue

    def crawl(self):
        for source, seed_urls, updates, oneoffs in self.sources:
            self.crawl_source(seed_urls, updates, oneoffs)
<|endoftext|>


<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Autheur: Matthieu Riou <matthieu.riou@etu.univ-nantes.fr>
# Version: v0.1
# Versions de python supportées: 3.3+ (à cause d'icalendar)
# Dépendances: icalendar, requests (pip install ; utiliser virtualenvwrapper)
# Notes: Pour choisir les groupes, aller remplir la fonction à la toute
# fin du programme.
# ToDo: Développer une interface graphique.

try:
	import codecs # support des encodages
	from datetime import datetime # time manipulation functions
	from datetime import date
	from datetime import time
	from icalendar import Calendar # support des .ics
	import locale # support of local locale (oh really ?), and internationalization
	import pytz # accurate timezone calculations
	from pytz import utc
	import requests # permet de réaliser simplement (plus qu'avec urllib2) des
	# requêtes HTTP
	import getpass # permet d'utilier getpass.getpass([prompt[, stream]]) pour
	# demander un mot de passe
	import sys
except ImportError:
	raise ImportError("Modules are required to run this program. Try `pip install icalendar requests`.")
	exit

class GestionDatetime:

	def __init__(self):
		self.paris = pytz.timezone('Europe/Paris')
		self.format = "%Y%m%dT%H%M%SZ"
        #datefind = datetime(2014, 4, 16, 11) # careful: 04 is invalid. 4
        # is. (octal numbers not allowed in python!)
        #find = datefind.strftime("%d/%m/%Y/%Hh%M")
        #ffind = utc.localize(datefind)
        #fffind = ffind.astimezone(paris)

	def getDatetime(self, my_date):
		date = my_date.to_ical()
		dtutcdate = utc.localize(datetime.strptime(date.decode(), self.format))
		dtdate = dtutcdate.astimezone(self.paris)

		return dtdate

class Connexion:

	def __init__(self):
		self.login = input("Login : ")
		self.mdp = getpass.getpass("Mot de passe : ")

		self.correspondance_group_tab = {"L3_Info" : "g11529", "M1_Atal" : "g78030", "L2_401" : "g93283", "L2_402" : "g115774", "L2_419" : "g7127","M1_Alma" : "g6935","M1_Oro" : "g9238", "L1_245" : "g51728", "L1_247" : "g94501", "L1_248" : "g115113", "L1_243K" : "g7057"}

	def connect(self, group):
		code_group = self.correspondance_group_tab[group]

		request = requests.get(
						'https://edt.univ-nantes.fr/sciences/' + code_group + '.ics',
						auth=(self.login, self.mdp),
						timeout=2)
		if not 200 <= request.status_code < 300:
			print("Error status while retrieving the ics file for group " + group + ".")
			exit

		return request

class Edt:

	def __init__(self):
		self.startWeek = 2 #Semaine de départ
		self.endWeek = 24 #Semaine de fin
		self.nbWeek = self.endWeek - self.startWeek + 1
		self.nbDayInWeek = 6 #Nombre de jour par semaine
		self.nbSlotInDay = 8 #Nombre de créneaux par jour

		self.connexion = Connexion()
		self.edt = {}

		self.nbSlot = self.nbWeek * self.nbDayInWeek * self.nbSlotInDay

		self.gestionDate = GestionDatetime()

		self.options = []

		self.convertDay = ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"]

	def addEdt(self, group):
		if group not in self.edt:
			e = self.connexion.connect(group)
			self.edt[group] = self.analyseEdt(e)

	def removeEdt(self, group):
		if group in self.edt:
			del self.edt[group]

	def addOption(self, option):
		if option not in self.options:
			self.options.append(option)

	def removeOption(self, option):
		if option in self.options:
			self.options.remove(option)

	def analyseEdt(self, edt):

		slots = [1]*self.nbSlot #At the beggining, all the slots are free

		for component in Calendar.from_ical(edt.text).walk():
			if component.name == 'VEVENT':
				start = self.gestionDate.getDatetime(component.get('DTSTART'))
				end = self.gestionDate.getDatetime(component.get('DTEND'))

				isoIndex = self.getIsoIndex(start)

				slot = Slot(start.time(), end.time())

				for index, defaultSlot in defaultSlots:
					if slot.intersect(defaultSlot): #If the slot intersect with a default slot
						slots[isoIndex + index] = 0 #This default slot isn't free

		return slots

	def getIsoIndex(self, datetime):
		isodate = datetime.isocalendar() #tuple (years, week, day) day from 1 to 7

		index = (isodate[1] - self.startWeek) * self.nbDayInWeek * self.nbSlotInDay
		index += (isodate[2] - 1) * self.nbSlotInDay

		return index

	def compare(self, list_groups): #Oh Oh ! Work for both list and dictionnary, it seems
		if not all(group in self.edt for group in list_groups) : #try except ?
			print("Erreur, groupe non présent")
			exit(1)

		res = []

		for group in list_groups:
			#Hou ! Le vilain test à chaque tour de boucle pour un seul cas qui se retrouve ici maintenant !
			#Mais j'ai pas trop trouvé comment faire autrement. Iterateur ?
			if len(res) == 0:
				res = self.edt[group]
			else:
				res = list(map(etbit, res, self.edt[group])) #Actually, we need to convert it in list, because we test len(res) after

		return list(res) #Must be convert here, because we want to return a list, and not an iterators (map object)

	def compareAll(self):
		if len(self.edt) == 0: #try except ?
			print("Erreur, il faut des groupes à comparer")
			exit(1)

		if len(self.options) == 0: #try except ?
			print("Erreur, il faut définir des options")
			exit(1)

		return self.compare(self.edt)

	def indexToResult(self, index):
		week = int(index / (self.nbDayInWeek * self.nbSlotInDay)) + self.startWeek
		day = int((index % (self.nbDayInWeek * self.nbSlotInDay)) / self.nbSlotInDay) + 1
		time = int((index % (self.nbDayInWeek * self.nbSlotInDay)) % self.nbSlotInDay)

		return week, day, time

	def resultToString(self, result):
		slot = [s for i,s in defaultSlots if i == result[2]][0]

		return "Semaine " + str(result[0]) + " " + self.convertDay[result[1]-1] + " " + slot.toString()

	def compareAndPrint(self):
		allResults = self.compareAll()

		results_tmp = [i for i,item in enumerate(allResults) if item == 1]

		results = map(self.resultToString, [item for item in map(self.indexToResult, results_tmp) if any(option.isIn(item) for option in self.options)])

		for e in results:
			print(e)

	def compareAllEachToEach(self):
		if len(self.edt) == 0: #try except ?
			print("Erreur, il faut des groupes à comparer")
			exit(1)

		if len(self.options) == 0: #try except ?
			print("Erreur, il faut définir des options")
			exit(1)

		return self.compareEachToEach(list(self.edt))

	def compareEachToEach(self, list_group):
		if len(list_group) == 0: #try except ?
			print("Erreur, il faut des groupes à comparer")
			exit(1)

		if len(list_group) == 1:
			return []
		else:
			res = []

			group = list_group[0]
			other_group = list_group[1:]

			for group2 in other_group:
				res.append({'groupe1' : group, 'groupe2' : group2, 'resultat' : self.compare([group, group2])})

			res.extend(self.compareEachToEach(other_group))

			return res

	def compareEachToEachAndPrint(self):
		allResults = self.compareAllEachToEach()

		for res in allResults:
			print("Comparaison entre " + res['groupe1'] + " et " + res['groupe2'] + " :")

			results_tmp = [i for i,item in enumerate(res['resultat']) if item == 1]

			results = map(self.resultToString, [item for item in map(self.indexToResult, results_tmp) if any(option.isIn(item) for option in self.options)])

			for e in results:
				print(e)

			print("\n")

class Slot:

	def __init__(self, start, end): #Only hours, not day, week and years

		self.start = start
		self.end = end

	def intersect(self, otherSlot):
		return (self.start <= otherSlot.start <= self.end) or (otherSlot.start <= self.start <= otherSlot.end)

	def toString(self):
		return "de " + self.start.strftime("%Hh%M") + " à " + self.end.strftime("%Hh%M")

defaultSlots = [
				(0, Slot(time(8), time(9,20))),
				(1, Slot(time(9,30), time(10, 50))),
				(2, Slot(time(11), time(12,20))),
				(3, Slot(time(12,30), time(13,50))),
				(4, Slot(time(14), time(15,20))),
				(5, Slot(time(15,30), time(16,50))),
				(6, Slot(time(17), time(18,20))),
				(7, Slot(time(18,20), time(19,30)))
]

class Option: #Filter slots we want

	def __init__(self, listWeek, listDay, listSlot):
		self.listWeek = listWeek
		self.listDay = listDay
		self.listSlot = listSlot

	def isIn(self, result): #result : (week, day, slot)
		return result[0] in self.listWeek and result[1] in self.listDay and result[2] in self.listSlot

def etbit(x, y): # comparaison logique d'indices de deux horaires
        # identique de deux groupes différents
        return x & y

def main(list_group): # raccourci final d'utilisation
	try:
		option = Option([18], [2,3], [0,1,2,4,5,6])
		option2 = Option([15], [1], [0,1,2,4,5,6])

		edt = Edt()

		edt.addOption(option)
		edt.addOption(option2)

		for group in list_group:
			edt.addEdt(group)

		edt.compareAndPrint()
		print("\n")
		edt.compareEachToEachAndPrint()

	except (KeyboardInterrupt, SystemExit):
		exit # quitte sans rien dire pour les évènements Ctrl-C, Ctrl-Q

if len(sys.argv) > 1:
	print(sys.argv[1:])
	main(sys.argv[1:])
else:
	main(["L1_245", "L1_248"])
<commit_msg>Correcting error catching for wrong login<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Autheur: Matthieu Riou <matthieu.riou@etu.univ-nantes.fr>
# Version: v0.1
# Versions de python supportées: 3.3+ (à cause d'icalendar)
# Dépendances: icalendar, requests (pip install ; utiliser virtualenvwrapper)
# Notes: Pour choisir les groupes, aller remplir la fonction à la toute
# fin du programme.
# ToDo: Développer une interface graphique.

try:
	import codecs # support des encodages
	from datetime import datetime # time manipulation functions
	from datetime import date
	from datetime import time
	from icalendar import Calendar # support des .ics
	import locale # support of local locale (oh really ?), and internationalization
	import pytz # accurate timezone calculations
	from pytz import utc
	import requests # permet de réaliser simplement (plus qu'avec urllib2) des
	# requêtes HTTP
	import getpass # permet d'utilier getpass.getpass([prompt[, stream]]) pour
	# demander un mot de passe
	import sys
except ImportError:
	raise ImportError("Modules are required to run this program. Try `pip install icalendar requests`.")
	exit

class GestionDatetime:

	def __init__(self):
		self.paris = pytz.timezone('Europe/Paris')
		self.format = "%Y%m%dT%H%M%SZ"
        #datefind = datetime(2014, 4, 16, 11) # careful: 04 is invalid. 4
        # is. (octal numbers not allowed in python!)
        #find = datefind.strftime("%d/%m/%Y/%Hh%M")
        #ffind = utc.localize(datefind)
        #fffind = ffind.astimezone(paris)

	def getDatetime(self, my_date):
		date = my_date.to_ical()
		dtutcdate = utc.localize(datetime.strptime(date.decode(), self.format))
		dtdate = dtutcdate.astimezone(self.paris)

		return dtdate

class Connexion:

	def __init__(self):
		self.login = input("Login : ")
		self.mdp = getpass.getpass("Mot de passe : ")

		self.correspondance_group_tab = {"L3_Info" : "g11529", "M1_Atal" : "g78030", "L2_401" : "g93283", "L2_402" : "g115774", "L2_419" : "g7127","M1_Alma" : "g6935","M1_Oro" : "g9238", "L1_245" : "g51728", "L1_247" : "g94501", "L1_248" : "g115113", "L1_243K" : "g7057"}

	def connect(self, group):
		code_group = self.correspondance_group_tab[group]

		request = requests.get(
						'https://edt.univ-nantes.fr/sciences/' + code_group + '.ics',
						auth=(self.login, self.mdp),
						timeout=2)
		if not 200 <= request.status_code < 300:
			print("Error status while retrieving the ics file for group " + group + ".")
			exit(1)

		return request

class Edt:

	def __init__(self):
		self.startWeek = 2 #Semaine de départ
		self.endWeek = 24 #Semaine de fin
		self.nbWeek = self.endWeek - self.startWeek + 1
		self.nbDayInWeek = 6 #Nombre de jour par semaine
		self.nbSlotInDay = 8 #Nombre de créneaux par jour

		self.connexion = Connexion()
		self.edt = {}

		self.nbSlot = self.nbWeek * self.nbDayInWeek * self.nbSlotInDay

		self.gestionDate = GestionDatetime()

		self.options = []

		self.convertDay = ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"]

	def addEdt(self, group):
		if group not in self.edt:
			e = self.connexion.connect(group)
			self.edt[group] = self.analyseEdt(e)

	def removeEdt(self, group):
		if group in self.edt:
			del self.edt[group]

	def addOption(self, option):
		if option not in self.options:
			self.options.append(option)

	def removeOption(self, option):
		if option in self.options:
			self.options.remove(option)

	def analyseEdt(self, edt):

		slots = [1]*self.nbSlot #At the beggining, all the slots are free

		for component in Calendar.from_ical(edt.text).walk():
			if component.name == 'VEVENT':
				start = self.gestionDate.getDatetime(component.get('DTSTART'))
				end = self.gestionDate.getDatetime(component.get('DTEND'))

				isoIndex = self.getIsoIndex(start)

				slot = Slot(start.time(), end.time())

				for index, defaultSlot in defaultSlots:
					if slot.intersect(defaultSlot): #If the slot intersect with a default slot
						slots[isoIndex + index] = 0 #This default slot isn't free

		return slots

	def getIsoIndex(self, datetime):
		isodate = datetime.isocalendar() #tuple (years, week, day) day from 1 to 7

		index = (isodate[1] - self.startWeek) * self.nbDayInWeek * self.nbSlotInDay
		index += (isodate[2] - 1) * self.nbSlotInDay

		return index

	def compare(self, list_groups): #Oh Oh ! Work for both list and dictionnary, it seems
		if not all(group in self.edt for group in list_groups) : #try except ?
			print("Erreur, groupe non présent")
			exit(1)

		res = []

		for group in list_groups:
			#Hou ! Le vilain test à chaque tour de boucle pour un seul cas qui se retrouve ici maintenant !
			#Mais j'ai pas trop trouvé comment faire autrement. Iterateur ?
			if len(res) == 0:
				res = self.edt[group]
			else:
				res = list(map(etbit, res, self.edt[group])) #Actually, we need to convert it in list, because we test len(res) after

		return list(res) #Must be convert here, because we want to return a list, and not an iterators (map object)

	def compareAll(self):
		if len(self.edt) == 0: #try except ?
			print("Erreur, il faut des groupes à comparer")
			exit(1)

		if len(self.options) == 0: #try except ?
			print("Erreur, il faut définir des options")
			exit(1)

		return self.compare(self.edt)

	def indexToResult(self, index):
		week = int(index / (self.nbDayInWeek * self.nbSlotInDay)) + self.startWeek
		day = int((index % (self.nbDayInWeek * self.nbSlotInDay)) / self.nbSlotInDay) + 1
		time = int((index % (self.nbDayInWeek * self.nbSlotInDay)) % self.nbSlotInDay)

		return week, day, time

	def resultToString(self, result):
		slot = [s for i,s in defaultSlots if i == result[2]][0]

		return "Semaine " + str(result[0]) + " " + self.convertDay[result[1]-1] + " " + slot.toString()

	def compareAndPrint(self):
		allResults = self.compareAll()

		results_tmp = [i for i,item in enumerate(allResults) if item == 1]

		results = map(self.resultToString, [item for item in map(self.indexToResult, results_tmp) if any(option.isIn(item) for option in self.options)])

		for e in results:
			print(e)

	def compareAllEachToEach(self):
		if len(self.edt) == 0: #try except ?
			print("Erreur, il faut des groupes à comparer")
			exit(1)

		if len(self.options) == 0: #try except ?
			print("Erreur, il faut définir des options")
			exit(1)

		return self.compareEachToEach(list(self.edt))

	def compareEachToEach(self, list_group):
		if len(list_group) == 0: #try except ?
			print("Erreur, il faut des groupes à comparer")
			exit(1)

		if len(list_group) == 1:
			return []
		else:
			res = []

			group = list_group[0]
			other_group = list_group[1:]

			for group2 in other_group:
				res.append({'groupe1' : group, 'groupe2' : group2, 'resultat' : self.compare([group, group2])})

			res.extend(self.compareEachToEach(other_group))

			return res

	def compareEachToEachAndPrint(self):
		allResults = self.compareAllEachToEach()

		for res in allResults:
			print("Comparaison entre " + res['groupe1'] + " et " + res['groupe2'] + " :")

			results_tmp = [i for i,item in enumerate(res['resultat']) if item == 1]

			results = map(self.resultToString, [item for item in map(self.indexToResult, results_tmp) if any(option.isIn(item) for option in self.options)])

			for e in results:
				print(e)

			print("\n")

class Slot:

	def __init__(self, start, end): #Only hours, not day, week and years

		self.start = start
		self.end = end

	def intersect(self, otherSlot):
		return (self.start <= otherSlot.start <= self.end) or (otherSlot.start <= self.start <= otherSlot.end)

	def toString(self):
		return "de " + self.start.strftime("%Hh%M") + " à " + self.end.strftime("%Hh%M")

defaultSlots = [
				(0, Slot(time(8), time(9,20))),
				(1, Slot(time(9,30), time(10, 50))),
				(2, Slot(time(11), time(12,20))),
				(3, Slot(time(12,30), time(13,50))),
				(4, Slot(time(14), time(15,20))),
				(5, Slot(time(15,30), time(16,50))),
				(6, Slot(time(17), time(18,20))),
				(7, Slot(time(18,20), time(19,30)))
]

class Option: #Filter slots we want

	def __init__(self, listWeek, listDay, listSlot):
		self.listWeek = listWeek
		self.listDay = listDay
		self.listSlot = listSlot

	def isIn(self, result): #result : (week, day, slot)
		return result[0] in self.listWeek and result[1] in self.listDay and result[2] in self.listSlot

def etbit(x, y): # comparaison logique d'indices de deux horaires
        # identique de deux groupes différents
        return x & y

def main(list_group): # raccourci final d'utilisation
	try:
		option = Option([18], [2,3], [0,1,2,4,5,6])
		option2 = Option([15], [1], [0,1,2,4,5,6])

		edt = Edt()

		edt.addOption(option)
		edt.addOption(option2)

		for group in list_group:
			edt.addEdt(group)

		edt.compareAndPrint()
		print("\n")
		edt.compareEachToEachAndPrint()

	except (KeyboardInterrupt, SystemExit):
		exit # quitte sans rien dire pour les évènements Ctrl-C, Ctrl-Q

if len(sys.argv) > 1:
	print(sys.argv[1:])
	main(sys.argv[1:])
else:
	main(["L1_245", "L1_248"])
<|endoftext|>


<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Autheur: Matthieu Riou <matthieu.riou@etu.univ-nantes.fr>
# Version: v0.1
# Versions de python supportées: 3.3+
# Dépendances: icalendar, requests (pip install ; utiliser virtualenvwrapper)
# Notes: Pour choisir les groupes, aller remplir la fonction à la toute
# fin du programme.
# ToDo: Modulariser la fonction appelante à la fin du programme ;
# Commenter ; Développer une interface graphique.

import codecs # support des encodages
from datetime import datetime # time manipulation functions
from datetime import time
from icalendar import Calendar # support des .ics
import locale # support of local locale (oh really ?), and internationalization
import pytz # accurate timezone calculations
from pytz import utc
import requests # permet de réaliser simplement (plus qu'avec urllib2) des
# requêtes HTTP
import getpass # permet d'utilier getpass.getpass([prompt[, stream]]) pour
# demander un mot de passe

correspondance_group_tab = {"L3_Info" : "g11529", "M1_Atal" : "g78030", "L2_401" : "g93283", "L2_402" : "g115774", "L2_419" : "g7127","M1_Alma" : "g6935","M1_Oro" : "g9238", "L1_245" : "g51728", "L1_247" : "g94501", "L1_248" : "g115113", "L1_243K" : "g7057"}
horaire_to_heure = ["8h00", "9h30", "11h00", "12h30", "14h00", "15h30", "17h00", "18h30"]
request = ""

def order(group):
	global request
	if request == "":
		request = connect(group) # objet reçu de la connexion via la
        # fonction connect définie plus bas.
	paris = pytz.timezone('Europe/Paris')
	format = "%Y%m%dT%H%M%SZ"
	datefind = datetime(2014, 4, 16, 11) # careful: 04 is invalid. 4
        # is. (octal numbers not allowed in python!)
	find = datefind.strftime("%d/%m/%Y/%Hh%M")
	ffind = utc.localize(datefind)
	fffind = ffind.astimezone(paris)

	#semaine de 4 à 22 (19 semaine)
	#semaine de 6 jour
	#jour de 8 crénaux
	#tableau de 19 * 6 * 8 = 912 case

	crenaux = [1]*912

	for component in Calendar.from_ical(request.text).walk():
		if component.name == 'VEVENT':
			start = component.get('DTSTART').to_ical()
			dtutcstart = utc.localize(datetime.strptime(start, format))
			dtstart = dtutcstart.astimezone(paris)
			fstart = dtstart.strftime("%d/%m/%Y/%Hh%M")

			end = component.get('DTEND').to_ical()
			dtutcend = utc.localize(datetime.strptime(end, format))
			dtend = dtutcend.astimezone(paris)
			fend = dtend.strftime("%d/%m/%Y/%Hh%M")

			getCrenaux(crenaux, dtstart, dtend)

	return crenaux

def getCrenaux(crenaux, start, end): #Pour le semestre 2 de 2014

	isodate = start.isocalendar() #tuple (annee, semaine, jour de 1 a 7)

	index = (isodate[1]-4) * 48
	index = index + (isodate[2]-1) * 8

	if intersect(time(8), time(9, 20), start.time(), end.time()):
		crenaux[index + 0] = 0

	if intersect(time(9, 30), time(10, 50), start.time(), end.time()):
		crenaux[index + 1] = 0

	if intersect(time(11), time(12, 20), start.time(), end.time()):
		crenaux[index + 2] = 0

	if intersect(time(12, 30), time(13, 50), start.time(), end.time()):
		crenaux[index + 3] = 0

	if intersect(time(14), time(15, 20), start.time(), end.time()):
		crenaux[index + 4] = 0

	if intersect(time(15, 30), time(16, 50), start.time(), end.time()):
		crenaux[index + 5] = 0

	if intersect(time(17), time(18, 20), start.time(), end.time()):
		crenaux[index + 6] = 0

def intersect(start1, end1, start2, end2):
	return (start1 <= start2 <= end1) or (start2 <= start1 <= end2)

def connect(group):
	locale.setlocale(locale.LC_ALL, 'fr_FR.utf8')
	login = input("Login : ")
	mdp = getpass.getpass("Mot de passe : ")
	request = requests.get(
		'https://edt.univ-nantes.fr/sciences/' + group + '.ics',
		auth=(login, mdp))
	if not 200 <= request.status_code < 300:
		return "Error status while retrieving the ics file."

	return request

def affiche_cours(start, end, description):
	format = "%Y%m%dT%H%M%SZ"
	paris = pytz.timezone('Europe/Paris')

	dtutcstart = utc.localize(datetime.strptime(start, format))
	dtutcend = utc.localize(datetime.strptime(end, format))
	dtstart = dtutcstart.astimezone(paris)
	dtend = dtutcend.astimezone(paris)
	result = u"Prochain cours le {date} de {start} à {end}".format(
		date=dtstart.strftime("%A %d/%m/%Y"),
		start=dtstart.strftime("%Hh%M"),
		end=dtend.strftime("%Hh%M"))

	return result

def etbit(x, y): # comparaison logique d'indices de deux horaires
        # identique de deux groupes différents
	return x & y

def compare_local(crenaux1, crenaux2):
	result = map(etbit, crenaux1, crenaux2)
	return result

def affiche_result(x): # indice x. en fonction de l'indice qui varie de 1
        # à 912, on affiche les semaine, jour et horaire de l'indice.
	semaine = x / 48
	jour = (x - semaine*48) / 8
	horaire = x - semaine*48 - jour*8

	heure = horaire_to_heure[horaire]

	if jour + 1 != 6 and heure != "12h30" and heure != "18h30" and (semaine == 13 or semaine == 14):
		print("Semaine {} jour {} horaire {}".format(semaine+4, jour+1, heure))

def compare(liste_crenaux):
	if len(liste_crenaux) == 1:
		for x in range(len(liste_crenaux[0])):
			if liste_crenaux[0][x] == 1:
				affiche_result(x)

		return liste_crenaux
	else:
		crenaux1 = liste_crenaux.pop()
		crenaux2 = liste_crenaux.pop()
		liste_crenaux.append(compare_local(crenaux1, crenaux2))
		return compare(liste_crenaux)

def ordergroup(liste_group):
	liste_result = []
	for group in liste_group:
		liste_result.append(order(group))

	return liste_result

def correspondance_group(group):
	global correspondance_group_tab
	return correspondance_group_tab[group]

def main(tableauGroupe): # raccourci final d'utilisation
        try:
                edtParGroupe = ordergroup(map(correspondance_group, tableauGroupe))
                # grouplist est la liste des groupes, de la forme suivante:
                # ["L1_245", "L1_247", ...]
                compare(edtParGroupe)
        except (KeyboardInterrupt, SystemExit):
                exit # quitte sans rien dire pour les évènements Ctrl-C, Ctrl-Q

main(["L1_245", "L1_247", "L1_248", "L1_243K", "L2_401", "L2_402",
      "L2_419", "M1_Alma", "M1_Atal", "M1_Oro"])
<commit_msg>Solved issue #4. The program is now fully working on python 3.3+<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Autheur: Matthieu Riou <matthieu.riou@etu.univ-nantes.fr>
# Version: v0.1
# Versions de python supportées: 3.3+ (à cause d'icalendar)
# Dépendances: icalendar, requests (pip install ; utiliser virtualenvwrapper)
# Notes: Pour choisir les groupes, aller remplir la fonction à la toute
# fin du programme.
# ToDo: Modulariser la fonction appelante à la fin du programme ;
# Commenter ; Développer une interface graphique.

import codecs # support des encodages
from datetime import datetime # time manipulation functions
from datetime import time
from icalendar import Calendar # support des .ics
import locale # support of local locale (oh really ?), and internationalization
import pytz # accurate timezone calculations
from pytz import utc
import requests # permet de réaliser simplement (plus qu'avec urllib2) des
# requêtes HTTP
import getpass # permet d'utilier getpass.getpass([prompt[, stream]]) pour
# demander un mot de passe

# VARIABLE GLOBALES
correspondance_group_tab = {"L3_Info" : "g11529", "M1_Atal" : "g78030", "L2_401" : "g93283", "L2_402" : "g115774", "L2_419" : "g7127","M1_Alma" : "g6935","M1_Oro" : "g9238", "L1_245" : "g51728", "L1_247" : "g94501", "L1_248" : "g115113", "L1_243K" : "g7057"}
horaire_to_heure = ["8h00", "9h30", "11h00", "12h30", "14h00", "15h30", "17h00", "18h30"]
request = ""

def order(group):
        req = connect(group) # objet reçu de la connexion via la
                # fonction connect. se connecte et définit request.
        paris = pytz.timezone('Europe/Paris')
        format = "%Y%m%dT%H%M%SZ"
        datefind = datetime(2014, 4, 16, 11) # careful: 04 is invalid. 4
        # is. (octal numbers not allowed in python!)
        find = datefind.strftime("%d/%m/%Y/%Hh%M")
        ffind = utc.localize(datefind)
        fffind = ffind.astimezone(paris)

        #semaine de 4 à 22 (19 semaine)
        #semaine de 6 jour
        #jour de 8 crénaux
        #tableau de 19 * 6 * 8 = 912 case

        crenaux = [1]*912

        for component in Calendar.from_ical(req.text).walk():
                if component.name == 'VEVENT':
                        start = component.get('DTSTART').to_ical()
                        dtutcstart = utc.localize(datetime.strptime(start.decode(), format))
                        dtstart = dtutcstart.astimezone(paris)
                        fstart = dtstart.strftime("%d/%m/%Y/%Hh%M")

                        end = component.get('DTEND').to_ical()
                        dtutcend = utc.localize(datetime.strptime(end.decode(), format))
                        dtend = dtutcend.astimezone(paris)
                        fend = dtend.strftime("%d/%m/%Y/%Hh%M")

                        getCrenaux(crenaux, dtstart, dtend)

        return crenaux

def getCrenaux(crenaux, start, end): #Pour le semestre 2 de 2014

        isodate = start.isocalendar() #tuple (annee, semaine, jour de 1 a 7)

        index = (isodate[1]-4) * 48
        index = index + (isodate[2]-1) * 8

        if intersect(time(8), time(9, 20), start.time(), end.time()):
                crenaux[index + 0] = 0

        if intersect(time(9, 30), time(10, 50), start.time(), end.time()):
                crenaux[index + 1] = 0

        if intersect(time(11), time(12, 20), start.time(), end.time()):
                crenaux[index + 2] = 0

        if intersect(time(12, 30), time(13, 50), start.time(), end.time()):
                crenaux[index + 3] = 0

        if intersect(time(14), time(15, 20), start.time(), end.time()):
                crenaux[index + 4] = 0

        if intersect(time(15, 30), time(16, 50), start.time(), end.time()):
                crenaux[index + 5] = 0

        if intersect(time(17), time(18, 20), start.time(), end.time()):
                crenaux[index + 6] = 0

def intersect(start1, end1, start2, end2):
        return (start1 <= start2 <= end1) or (start2 <= start1 <= end2)

def affiche_cours(start, end, description):
        format = "%Y%m%dT%H%M%SZ"
        paris = pytz.timezone('Europe/Paris')

        dtutcstart = utc.localize(datetime.strptime(start, format))
        dtutcend = utc.localize(datetime.strptime(end, format))
        dtstart = dtutcstart.astimezone(paris)
        dtend = dtutcend.astimezone(paris)
        result = u"Prochain cours le {date} de {start} à {end}".format(
                date=dtstart.strftime("%A %d/%m/%Y"),
                start=dtstart.strftime("%Hh%M"),
                end=dtend.strftime("%Hh%M"))

        return result

def etbit(x, y): # comparaison logique d'indices de deux horaires
        # identique de deux groupes différents
        return x & y

def compare_local(crenaux1, crenaux2):
        result = map(etbit, crenaux1, crenaux2)
        return result

def affiche_result(x): # indice x. en fonction de l'indice qui varie de 1
        # à 912, on affiche les semaine, jour et horaire de l'indice.
        semaine = x / 48
        jour = (x - semaine*48) / 8
        horaire = int(x - semaine*48 - jour*8)

        heure = horaire_to_heure[horaire]

        if jour + 1 != 6 and heure != "12h30" and heure != "18h30" and (semaine == 13 or semaine == 14):
                print("Semaine {} jour {} horaire {}".format(semaine+4, jour+1, heure))

def compare(liste_crenaux):
        if len(liste_crenaux) == 1:
                lst = list(liste_crenaux[0]) # on rend la liste subscriptable
                for x in range(len(lst)):
                        if lst[x] == 1:
                                affiche_result(x)

                return liste_crenaux
        else:
                crenaux1 = liste_crenaux.pop()
                crenaux2 = liste_crenaux.pop()
                liste_crenaux.append(compare_local(crenaux1, crenaux2))
                return compare(liste_crenaux)

def ordergroup(liste_group):
        liste_result = []
        for group in liste_group:
                liste_result.append(order(group))

        return list(liste_result)

def correspondance_group(group):
        global correspondance_group_tab
        return correspondance_group_tab[group]

def main(tableauGroupe): # raccourci final d'utilisation
        try:
                edtParGroupe = ordergroup(list(map(correspondance_group, tableauGroupe)))
                # grouplist est la liste des groupes, de la forme suivante:
                # ["L1_245", "L1_247", ...]
                compare(edtParGroupe)
        except (KeyboardInterrupt, SystemExit):
                exit # quitte sans rien dire pour les évènements Ctrl-C, Ctrl-Q

# main(["L1_245", "L1_247", "L1_248", "L1_243K", "L2_401", "L2_402",
#       "L2_419", "M1_Alma", "M1_Atal", "M1_Oro"])
main(["L1_245", "M1_Oro"])
<|endoftext|>


<commit_before>from .credentials import PartnerCredentials
from .endpoints import ALL, ENDPOINTS, GET
from .managers import Manager

class Myob:
    """ An ORM-like interface to the MYOB API. """
    def __init__(self, credentials):
        if not isinstance(credentials, PartnerCredentials):
            raise TypeError(
                'Expected a Credentials instance, got %s.' % (
                    type(credentials).__name__,
                )
            )
        self.credentials = credentials
        self.companyfiles = CompanyFiles(credentials)
        self._manager = Manager('', credentials, raw_endpoints=[
            (GET, 'Info/', 'Return API build information for each individual endpoint.'),
        ])

    def info(self):
        return self._manager.info()

    def __repr__(self):
        return 'Myob:\n    %s' % '\n    '.join(['companyfiles', 'info'])

class CompanyFiles:
    def __init__(self, credentials):
        self.credentials = credentials
        self._manager = Manager('', self.credentials, raw_endpoints=[
            (ALL, '', 'Return a list of company files.'),
            (GET, '[id]/', 'List endpoints available for a company file.'),
        ])
        self._manager.name = 'CompanyFile'

    def all(self):
        raw_companyfiles = self._manager.all()
        return [CompanyFile(raw_companyfile, self.credentials) for raw_companyfile in raw_companyfiles]

    def get(self, id, call=True):
        if call:
            raw_companyfile = self._manager.get(id=id)['CompanyFile']
        else:
            raw_companyfile = {'Id': id}
        return CompanyFile(raw_companyfile, self.credentials)

    def __repr__(self):
        return self._manager.__repr__()

class CompanyFile:
    def __init__(self, raw, credentials):
        self.id = raw['Id']
        self.name = raw.get('Name')
        self.data = raw  # Dump remaining raw data here.
        self.credentials = credentials
        for k, v in ENDPOINTS.items():
            setattr(self, v['name'], Manager(k, credentials, endpoints=v['methods'], company_id=self.id))

    def __repr__(self):
        return 'CompanyFile:\n    %s' % '\n    '.join(sorted(v['name'] for v in ENDPOINTS.values()))
<commit_msg>Ensure companyfile credentials are passed in companyfiles.get method<commit_after>from .credentials import PartnerCredentials
from .endpoints import ALL, ENDPOINTS, GET
from .managers import Manager

class Myob:
    """ An ORM-like interface to the MYOB API. """
    def __init__(self, credentials):
        if not isinstance(credentials, PartnerCredentials):
            raise TypeError(
                'Expected a Credentials instance, got %s.' % (
                    type(credentials).__name__,
                )
            )
        self.credentials = credentials
        self.companyfiles = CompanyFiles(credentials)
        self._manager = Manager('', credentials, raw_endpoints=[
            (GET, 'Info/', 'Return API build information for each individual endpoint.'),
        ])

    def info(self):
        return self._manager.info()

    def __repr__(self):
        return 'Myob:\n    %s' % '\n    '.join(['companyfiles', 'info'])

class CompanyFiles:
    def __init__(self, credentials):
        self.credentials = credentials
        self._manager = Manager('', self.credentials, raw_endpoints=[
            (ALL, '', 'Return a list of company files.'),
            (GET, '[id]/', 'List endpoints available for a company file.'),
        ])
        self._manager.name = 'CompanyFile'

    def all(self):
        raw_companyfiles = self._manager.all()
        return [CompanyFile(raw_companyfile, self.credentials) for raw_companyfile in raw_companyfiles]

    def get(self, id, call=True):
        if call:
            # raw_companyfile = self._manager.get(id=id)['CompanyFile']
            # NOTE: Annoyingly, we need to pass company_id to the manager, else we won't have permission
            # on the GET endpoint. The only way we currently allow passing company_id is by setting it on the manager,
            # and we can't do that on init, as this is a manager for company files plural..
            # Reluctant to change manager code, as it would add confusion if the inner method let you override the company_id.
            manager = Manager('', self.credentials, raw_endpoints=[(GET, '', '')], company_id=id)
            raw_companyfile = manager.get(id=id)['CompanyFile']
        else:
            raw_companyfile = {'Id': id}
        return CompanyFile(raw_companyfile, self.credentials)

    def __repr__(self):
        return self._manager.__repr__()

class CompanyFile:
    def __init__(self, raw, credentials):
        self.id = raw['Id']
        self.name = raw.get('Name')
        self.data = raw  # Dump remaining raw data here.
        self.credentials = credentials
        for k, v in ENDPOINTS.items():
            setattr(self, v['name'], Manager(k, credentials, endpoints=v['methods'], company_id=self.id))

    def __repr__(self):
        return 'CompanyFile:\n    %s' % '\n    '.join(sorted(v['name'] for v in ENDPOINTS.values()))
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-

"""
The MIT License (MIT)

Copyright (c) 2015-2020 Rapptz

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""

import datetime

from . import utils
from .colour import Colour

class _EmptyEmbed:
    def __bool__(self):
        return False

    def __repr__(self):
        return 'Embed.Empty'

    def __len__(self):
        return 0

EmptyEmbed = _EmptyEmbed()

class EmbedProxy:
    def __init__(self, layer):
        self.__dict__.update(layer)

    def __len__(self):
        return len(self.__dict__)

    def __repr__(self):
        return 'EmbedProxy(%s)' % ', '.join(('%s=%r' % (k, v) for k, v in self.__dict__.items() if not k.startswith('_')))

    def __getattr__(self, attr):
        return EmptyEmbed

class Embed:
    """Represents a Discord embed.

    .. container:: operations

        .. describe:: len(x)

            Returns the total size of the embed.
            Useful for checking if it's within the 6000 character limit.

    Certain properties return an ``EmbedProxy``, a type
    that acts similar to a regular :class:`dict` except using dotted access,
    e.g. ``embed.author.icon_url``. If the attribute
    is invalid or empty, then a special sentinel value is returned,
    :attr:`Embed.Empty`.

    For ease of use, all parameters that expect a :class:`str` are implicitly
    casted to :class:`str` for you.

    Attributes
    -----------
    title: :class:`str`
        The title of the embed.
        This can be set during initialisation.
    type: :class:`str`
        The type of embed. Usually "rich".
        This can be set during initialisation.
    description: :class:`str`
        The description of the embed.
        This can be set during initialisation.
    url: :class:`str`
        The URL of the embed.
        This can be set during initialisation.
    timestamp: :class:`datetime.datetime`
        The timestamp of the embed content. This could be a naive or aware datetime.
    colour: Union[:class:`Colour`, :class:`int`]
        The colour code of the embed. Aliased to ``color`` as well.
        This can be set during initialisation.
    Empty
        A special sentinel value used by ``EmbedProxy`` and this class
        to denote that the value or attribute is empty.
    """

    __slots__ = ('title', 'url', 'type', '_timestamp', '_colour', '_footer',
                 '_image', '_thumbnail', '_video', '_provider', '_author',
                 '_fields', 'description')

    Empty = EmptyEmbed

    def __init__(self, **kwargs):
        # swap the colour/color aliases
        try:
            colour = kwargs['colour']
        except KeyError:
            colour = kwargs.get('color', EmptyEmbed)

        self.colour = colour
        self.title = kwargs.get('title', EmptyEmbed)
        self.type = kwargs.get('type', 'rich')
        self.url = kwargs.get('url', EmptyEmbed)
        self.description = kwargs.get('description', EmptyEmbed)

        try:
            timestamp = kwargs['timestamp']
        except KeyError:
            pass
        else:
            self.timestamp = timestamp

    @classmethod
    def from_dict(cls, data):
        """Converts a :class:`dict` to a :class:`Embed` provided it is in the
        format that Discord expects it to be in.

        You can find out about this format in the `official Discord documentation`__.

        .. _DiscordDocs: https://discord.com/developers/docs/resources/channel#embed-object

        __ DiscordDocs_

        Parameters
        -----------
        data: :class:`dict`
            The dictionary to convert into an embed.
        """
        # we are bypassing __init__ here since it doesn't apply here
        self = cls.__new__(cls)

        # fill in the basic fields

        self.title = data.get('title', EmptyEmbed)
        self.type = data.get('type', EmptyEmbed)
        self.description = data.get('description', EmptyEmbed)
        self.url = data.get('url', EmptyEmbed)

        # try to fill in the more rich fields

        try:
            self._colour = Colour(value=data['color'])
        except KeyError:
            pass

        try:
            self._timestamp = utils.parse_time(data['timestamp'])
        except KeyError:
            pass

        for attr in ('thumbnail', 'video', 'provider', 'author', 'fields', 'image', 'footer'):
            try:
                value = data[attr]
            except KeyError:
                continue
            else:
                setattr(self, '_' + attr, value)

        return self

    def copy(self):
        """Returns a shallow copy of the embed."""
        return Embed.from_dict(self.to_dict())

    def __len__(self):
        total = len(self.title) + len(self.description)
        for field in getattr(self, '_fields', []):
            total += len(field['name']) + len(field['value'])

        try:
            footer = self._footer
        except AttributeError:
            pass
        else:
            total += len(footer['text'])

        try:
            author = self._author
        except AttributeError:
            pass
        else:
            total += len(author['name'])

        return total

    @property
    def colour(self):
        return getattr(self, '_colour', EmptyEmbed)

    @colour.setter
    def colour(self, value):
        if isinstance(value, (Colour, _EmptyEmbed)):
            self._colour = value
        elif isinstance(value, int):
            self._colour = Colour(value=value)
        else:
            raise TypeError('Expected discord.Colour, int, or Embed.Empty but received %s instead.' % value.__class__.__name__)

    color = colour

    @property
    def timestamp(self):
        return getattr(self, '_timestamp', EmptyEmbed)

    @timestamp.setter
    def timestamp(self, value):
        if isinstance(value, (datetime.datetime, _EmptyEmbed)):
            self._timestamp = value
        else:
            raise TypeError("Expected datetime.datetime or Embed.Empty received %s instead" % value.__class__.__name__)

    @property
    def footer(self):
        """Union[:class:`EmbedProxy`, :attr:`Empty`]: Returns an ``EmbedProxy`` denoting the footer contents.

        See :meth:`set_footer` for possible values you can access.

        If the attribute has no value then :attr:`Empty` is returned.
        """
        return EmbedProxy(getattr(self, '_footer', {}))

    def set_footer(self, *, text=EmptyEmbed, icon_url=EmptyEmbed):
        """Sets the footer for the embed content.

        This function returns the class instance to allow for fluent-style
        chaining.

        Parameters
        -----------
        text: :class:`str`
            The footer text.
        icon_url: :class:`str`
            The URL of the footer icon. Only HTTP(S) is supported.
        """

        self._footer = {}
        if text is not EmptyEmbed:
            self._footer['text'] = str(text)

        if icon_url is not EmptyEmbed:
            self._footer['icon_url'] = str(icon_url)

        return self

    @property
    def image(self):
        """Union[:class:`EmbedProxy`, :attr:`Empty`]: Returns an ``EmbedProxy`` denoting the image contents.

        Possible attributes you can access are:

        - ``url``
        - ``proxy_url``
        - ``width``
        - ``height``

        If the attribute has no value then :attr:`Empty` is returned.
        """
        return EmbedProxy(getattr(self, '_image', {}))

    def set_image(self, *, url):
        """Sets the image for the embed content.

        This function returns the class instance to allow for fluent-style
        chaining.

        .. versionchanged:: 1.4
            Passing :attr:`Empty` removes the image.

        Parameters
        -----------
        url: :class:`str`
            The source URL for the image. Only HTTP(S) is supported.
        """

        if url is EmptyEmbed:
            del self._image
        else:
            self._image = {
                'url': str(url)
            }

        return self

    @property
    def thumbnail(self):
        """Union[:class:`EmbedProxy`, :attr:`Empty`]: Returns an ``EmbedProxy`` denoting the thumbnail contents.

        Possible attributes you can access are:

        - ``url``
        - ``proxy_url``
        - ``width``
        - ``height``

        If the attribute has no value then :attr:`Empty` is returned.
        """
        return EmbedProxy(getattr(self, '_thumbnail', {}))

    def set_thumbnail(self, *, url):
        """Sets the thumbnail for the embed content.

        This function returns the class instance to allow for fluent-style
        chaining.

        .. versionchanged:: 1.4
            Passing :attr:`Empty` removes the thumbnail.

        Parameters
        -----------
        url: :class:`str`
            The source URL for the thumbnail. Only HTTP(S) is supported.
        """

        if url is EmptyEmbed:
            del self._thumbnail
        else:
            self._thumbnail = {
                'url': str(url)
            }

        return self

    @property
    def video(self):
        """Union[:class:`EmbedProxy`, :attr:`Empty`]: Returns an ``EmbedProxy`` denoting the video contents.

        Possible attributes include:

        - ``url`` for the video URL.
        - ``height`` for the video height.
        - ``width`` for the video width.

        If the attribute has no value then :attr:`Empty` is returned.
        """
        return EmbedProxy(getattr(self, '_video', {}))

    @property
    def provider(self):
        """Union[:class:`EmbedProxy`, :attr:`Empty`]: Returns an ``EmbedProxy`` denoting the provider contents.

        The only attributes that might be accessed are ``name`` and ``url``.

        If the attribute has no value then :attr:`Empty` is returned.
        """
        return EmbedProxy(getattr(self, '_provider', {}))

    @property
    def author(self):
        """Union[:class:`EmbedProxy`, :attr:`Empty`]: Returns an ``EmbedProxy`` denoting the author contents.

        See :meth:`set_author` for possible values you can access.

        If the attribute has no value then :attr:`Empty` is returned.
        """
        return EmbedProxy(getattr(self, '_author', {}))

    def set_author(self, *, name, url=EmptyEmbed, icon_url=EmptyEmbed):
        """Sets the author for the embed content.

        This function returns the class instance to allow for fluent-style
        chaining.

        Parameters
        -----------
        name: :class:`str`
            The name of the author.
        url: :class:`str`
            The URL for the author.
        icon_url: :class:`str`
            The URL of the author icon. Only HTTP(S) is supported.
        """

        self._author = {
            'name': str(name)
        }

        if url is not EmptyEmbed:
            self._author['url'] = str(url)

        if icon_url is not EmptyEmbed:
            self._author['icon_url'] = str(icon_url)

        return self

    def remove_author(self):
        """Clears embed's author information.

        This function returns the class instance to allow for fluent-style
        chaining.

        .. versionadded:: 1.4
        """
        try:
            del self._author
        except AttributeError:
            pass

        return self

    @property
    def fields(self):
        """Union[List[:class:`EmbedProxy`], :attr:`Empty`]: Returns a :class:`list` of ``EmbedProxy`` denoting the field contents.

        See :meth:`add_field` for possible values you can access.

        If the attribute has no value then :attr:`Empty` is returned.
        """
        return [EmbedProxy(d) for d in getattr(self, '_fields', [])]

    def add_field(self, *, name, value, inline=True):
        """Adds a field to the embed object.

        This function returns the class instance to allow for fluent-style
        chaining.

        Parameters
        -----------
        name: :class:`str`
            The name of the field.
        value: :class:`str`
            The value of the field.
        inline: :class:`bool`
            Whether the field should be displayed inline.
        """

        field = {
            'inline': inline,
            'name': str(name),
            'value': str(value)
        }

        try:
            self._fields.append(field)
        except AttributeError:
            self._fields = [field]

        return self

    def insert_field_at(self, index, *, name, value, inline=True):
        """Inserts a field before a specified index to the embed.

        This function returns the class instance to allow for fluent-style
        chaining.

        .. versionadded:: 1.2

        Parameters
        -----------
        index: :class:`int`
            The index of where to insert the field.
        name: :class:`str`
            The name of the field.
        value: :class:`str`
            The value of the field.
        inline: :class:`bool`
            Whether the field should be displayed inline.
        """

        field = {
            'inline': inline,
            'name': str(name),
            'value': str(value)
        }

        try:
            self._fields.insert(index, field)
        except AttributeError:
            self._fields = [field]

        return self

    def clear_fields(self):
        """Removes all fields from this embed."""
        try:
            self._fields.clear()
        except AttributeError:
            self._fields = []

    def remove_field(self, index):
        """Removes a field at a specified index.

        If the index is invalid or out of bounds then the error is
        silently swallowed.

        .. note::

            When deleting a field by index, the index of the other fields
            shift to fill the gap just like a regular list.

        Parameters
        -----------
        index: :class:`int`
            The index of the field to remove.
        """
        try:
            del self._fields[index]
        except (AttributeError, IndexError):
            pass

    def set_field_at(self, index, *, name, value, inline=True):
        """Modifies a field to the embed object.

        The index must point to a valid pre-existing field.

        This function returns the class instance to allow for fluent-style
        chaining.

        Parameters
        -----------
        index: :class:`int`
            The index of the field to modify.
        name: :class:`str`
            The name of the field.
        value: :class:`str`
            The value of the field.
        inline: :class:`bool`
            Whether the field should be displayed inline.

        Raises
        -------
        IndexError
            An invalid index was provided.
        """

        try:
            field = self._fields[index]
        except (TypeError, IndexError, AttributeError):
            raise IndexError('field index out of range')

        field['name'] = str(name)
        field['value'] = str(value)
        field['inline'] = inline
        return self

    def to_dict(self):
        """Converts this embed object into a dict."""

        # add in the raw data into the dict
        result = {
            key[1:]: getattr(self, key)
            for key in self.__slots__
            if key[0] == '_' and hasattr(self, key)
        }

        # deal with basic convenience wrappers

        try:
            colour = result.pop('colour')
        except KeyError:
            pass
        else:
            if colour:
                result['color'] = colour.value

        try:
            timestamp = result.pop('timestamp')
        except KeyError:
            pass
        else:
            if timestamp:
                if timestamp.tzinfo:
                    result['timestamp'] = timestamp.astimezone(tz=datetime.timezone.utc).isoformat()
                else:
                    result['timestamp'] = timestamp.replace(tzinfo=datetime.timezone.utc).isoformat()

        # add in the non raw attribute ones
        if self.type:
            result['type'] = self.type

        if self.description:
            result['description'] = self.description

        if self.url:
            result['url'] = self.url

        if self.title:
            result['title'] = self.title

        return result
<commit_msg>Fix error when trying to clear empty image/thumbnail in embeds<commit_after># -*- coding: utf-8 -*-

"""
The MIT License (MIT)

Copyright (c) 2015-2020 Rapptz

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""

import datetime

from . import utils
from .colour import Colour

class _EmptyEmbed:
    def __bool__(self):
        return False

    def __repr__(self):
        return 'Embed.Empty'

    def __len__(self):
        return 0

EmptyEmbed = _EmptyEmbed()

class EmbedProxy:
    def __init__(self, layer):
        self.__dict__.update(layer)

    def __len__(self):
        return len(self.__dict__)

    def __repr__(self):
        return 'EmbedProxy(%s)' % ', '.join(('%s=%r' % (k, v) for k, v in self.__dict__.items() if not k.startswith('_')))

    def __getattr__(self, attr):
        return EmptyEmbed

class Embed:
    """Represents a Discord embed.

    .. container:: operations

        .. describe:: len(x)

            Returns the total size of the embed.
            Useful for checking if it's within the 6000 character limit.

    Certain properties return an ``EmbedProxy``, a type
    that acts similar to a regular :class:`dict` except using dotted access,
    e.g. ``embed.author.icon_url``. If the attribute
    is invalid or empty, then a special sentinel value is returned,
    :attr:`Embed.Empty`.

    For ease of use, all parameters that expect a :class:`str` are implicitly
    casted to :class:`str` for you.

    Attributes
    -----------
    title: :class:`str`
        The title of the embed.
        This can be set during initialisation.
    type: :class:`str`
        The type of embed. Usually "rich".
        This can be set during initialisation.
    description: :class:`str`
        The description of the embed.
        This can be set during initialisation.
    url: :class:`str`
        The URL of the embed.
        This can be set during initialisation.
    timestamp: :class:`datetime.datetime`
        The timestamp of the embed content. This could be a naive or aware datetime.
    colour: Union[:class:`Colour`, :class:`int`]
        The colour code of the embed. Aliased to ``color`` as well.
        This can be set during initialisation.
    Empty
        A special sentinel value used by ``EmbedProxy`` and this class
        to denote that the value or attribute is empty.
    """

    __slots__ = ('title', 'url', 'type', '_timestamp', '_colour', '_footer',
                 '_image', '_thumbnail', '_video', '_provider', '_author',
                 '_fields', 'description')

    Empty = EmptyEmbed

    def __init__(self, **kwargs):
        # swap the colour/color aliases
        try:
            colour = kwargs['colour']
        except KeyError:
            colour = kwargs.get('color', EmptyEmbed)

        self.colour = colour
        self.title = kwargs.get('title', EmptyEmbed)
        self.type = kwargs.get('type', 'rich')
        self.url = kwargs.get('url', EmptyEmbed)
        self.description = kwargs.get('description', EmptyEmbed)

        try:
            timestamp = kwargs['timestamp']
        except KeyError:
            pass
        else:
            self.timestamp = timestamp

    @classmethod
    def from_dict(cls, data):
        """Converts a :class:`dict` to a :class:`Embed` provided it is in the
        format that Discord expects it to be in.

        You can find out about this format in the `official Discord documentation`__.

        .. _DiscordDocs: https://discord.com/developers/docs/resources/channel#embed-object

        __ DiscordDocs_

        Parameters
        -----------
        data: :class:`dict`
            The dictionary to convert into an embed.
        """
        # we are bypassing __init__ here since it doesn't apply here
        self = cls.__new__(cls)

        # fill in the basic fields

        self.title = data.get('title', EmptyEmbed)
        self.type = data.get('type', EmptyEmbed)
        self.description = data.get('description', EmptyEmbed)
        self.url = data.get('url', EmptyEmbed)

        # try to fill in the more rich fields

        try:
            self._colour = Colour(value=data['color'])
        except KeyError:
            pass

        try:
            self._timestamp = utils.parse_time(data['timestamp'])
        except KeyError:
            pass

        for attr in ('thumbnail', 'video', 'provider', 'author', 'fields', 'image', 'footer'):
            try:
                value = data[attr]
            except KeyError:
                continue
            else:
                setattr(self, '_' + attr, value)

        return self

    def copy(self):
        """Returns a shallow copy of the embed."""
        return Embed.from_dict(self.to_dict())

    def __len__(self):
        total = len(self.title) + len(self.description)
        for field in getattr(self, '_fields', []):
            total += len(field['name']) + len(field['value'])

        try:
            footer = self._footer
        except AttributeError:
            pass
        else:
            total += len(footer['text'])

        try:
            author = self._author
        except AttributeError:
            pass
        else:
            total += len(author['name'])

        return total

    @property
    def colour(self):
        return getattr(self, '_colour', EmptyEmbed)

    @colour.setter
    def colour(self, value):
        if isinstance(value, (Colour, _EmptyEmbed)):
            self._colour = value
        elif isinstance(value, int):
            self._colour = Colour(value=value)
        else:
            raise TypeError('Expected discord.Colour, int, or Embed.Empty but received %s instead.' % value.__class__.__name__)

    color = colour

    @property
    def timestamp(self):
        return getattr(self, '_timestamp', EmptyEmbed)

    @timestamp.setter
    def timestamp(self, value):
        if isinstance(value, (datetime.datetime, _EmptyEmbed)):
            self._timestamp = value
        else:
            raise TypeError("Expected datetime.datetime or Embed.Empty received %s instead" % value.__class__.__name__)

    @property
    def footer(self):
        """Union[:class:`EmbedProxy`, :attr:`Empty`]: Returns an ``EmbedProxy`` denoting the footer contents.

        See :meth:`set_footer` for possible values you can access.

        If the attribute has no value then :attr:`Empty` is returned.
        """
        return EmbedProxy(getattr(self, '_footer', {}))

    def set_footer(self, *, text=EmptyEmbed, icon_url=EmptyEmbed):
        """Sets the footer for the embed content.

        This function returns the class instance to allow for fluent-style
        chaining.

        Parameters
        -----------
        text: :class:`str`
            The footer text.
        icon_url: :class:`str`
            The URL of the footer icon. Only HTTP(S) is supported.
        """

        self._footer = {}
        if text is not EmptyEmbed:
            self._footer['text'] = str(text)

        if icon_url is not EmptyEmbed:
            self._footer['icon_url'] = str(icon_url)

        return self

    @property
    def image(self):
        """Union[:class:`EmbedProxy`, :attr:`Empty`]: Returns an ``EmbedProxy`` denoting the image contents.

        Possible attributes you can access are:

        - ``url``
        - ``proxy_url``
        - ``width``
        - ``height``

        If the attribute has no value then :attr:`Empty` is returned.
        """
        return EmbedProxy(getattr(self, '_image', {}))

    def set_image(self, *, url):
        """Sets the image for the embed content.

        This function returns the class instance to allow for fluent-style
        chaining.

        .. versionchanged:: 1.4
            Passing :attr:`Empty` removes the image.

        Parameters
        -----------
        url: :class:`str`
            The source URL for the image. Only HTTP(S) is supported.
        """

        if url is EmptyEmbed:
            try:
                del self._image
            except AttributeError:
                pass
        else:
            self._image = {
                'url': str(url)
            }

        return self

    @property
    def thumbnail(self):
        """Union[:class:`EmbedProxy`, :attr:`Empty`]: Returns an ``EmbedProxy`` denoting the thumbnail contents.

        Possible attributes you can access are:

        - ``url``
        - ``proxy_url``
        - ``width``
        - ``height``

        If the attribute has no value then :attr:`Empty` is returned.
        """
        return EmbedProxy(getattr(self, '_thumbnail', {}))

    def set_thumbnail(self, *, url):
        """Sets the thumbnail for the embed content.

        This function returns the class instance to allow for fluent-style
        chaining.

        .. versionchanged:: 1.4
            Passing :attr:`Empty` removes the thumbnail.

        Parameters
        -----------
        url: :class:`str`
            The source URL for the thumbnail. Only HTTP(S) is supported.
        """

        if url is EmptyEmbed:
            try:
                del self._thumbnail
            except AttributeError:
                pass
        else:
            self._thumbnail = {
                'url': str(url)
            }

        return self

    @property
    def video(self):
        """Union[:class:`EmbedProxy`, :attr:`Empty`]: Returns an ``EmbedProxy`` denoting the video contents.

        Possible attributes include:

        - ``url`` for the video URL.
        - ``height`` for the video height.
        - ``width`` for the video width.

        If the attribute has no value then :attr:`Empty` is returned.
        """
        return EmbedProxy(getattr(self, '_video', {}))

    @property
    def provider(self):
        """Union[:class:`EmbedProxy`, :attr:`Empty`]: Returns an ``EmbedProxy`` denoting the provider contents.

        The only attributes that might be accessed are ``name`` and ``url``.

        If the attribute has no value then :attr:`Empty` is returned.
        """
        return EmbedProxy(getattr(self, '_provider', {}))

    @property
    def author(self):
        """Union[:class:`EmbedProxy`, :attr:`Empty`]: Returns an ``EmbedProxy`` denoting the author contents.

        See :meth:`set_author` for possible values you can access.

        If the attribute has no value then :attr:`Empty` is returned.
        """
        return EmbedProxy(getattr(self, '_author', {}))

    def set_author(self, *, name, url=EmptyEmbed, icon_url=EmptyEmbed):
        """Sets the author for the embed content.

        This function returns the class instance to allow for fluent-style
        chaining.

        Parameters
        -----------
        name: :class:`str`
            The name of the author.
        url: :class:`str`
            The URL for the author.
        icon_url: :class:`str`
            The URL of the author icon. Only HTTP(S) is supported.
        """

        self._author = {
            'name': str(name)
        }

        if url is not EmptyEmbed:
            self._author['url'] = str(url)

        if icon_url is not EmptyEmbed:
            self._author['icon_url'] = str(icon_url)

        return self

    def remove_author(self):
        """Clears embed's author information.

        This function returns the class instance to allow for fluent-style
        chaining.

        .. versionadded:: 1.4
        """
        try:
            del self._author
        except AttributeError:
            pass

        return self

    @property
    def fields(self):
        """Union[List[:class:`EmbedProxy`], :attr:`Empty`]: Returns a :class:`list` of ``EmbedProxy`` denoting the field contents.

        See :meth:`add_field` for possible values you can access.

        If the attribute has no value then :attr:`Empty` is returned.
        """
        return [EmbedProxy(d) for d in getattr(self, '_fields', [])]

    def add_field(self, *, name, value, inline=True):
        """Adds a field to the embed object.

        This function returns the class instance to allow for fluent-style
        chaining.

        Parameters
        -----------
        name: :class:`str`
            The name of the field.
        value: :class:`str`
            The value of the field.
        inline: :class:`bool`
            Whether the field should be displayed inline.
        """

        field = {
            'inline': inline,
            'name': str(name),
            'value': str(value)
        }

        try:
            self._fields.append(field)
        except AttributeError:
            self._fields = [field]

        return self

    def insert_field_at(self, index, *, name, value, inline=True):
        """Inserts a field before a specified index to the embed.

        This function returns the class instance to allow for fluent-style
        chaining.

        .. versionadded:: 1.2

        Parameters
        -----------
        index: :class:`int`
            The index of where to insert the field.
        name: :class:`str`
            The name of the field.
        value: :class:`str`
            The value of the field.
        inline: :class:`bool`
            Whether the field should be displayed inline.
        """

        field = {
            'inline': inline,
            'name': str(name),
            'value': str(value)
        }

        try:
            self._fields.insert(index, field)
        except AttributeError:
            self._fields = [field]

        return self

    def clear_fields(self):
        """Removes all fields from this embed."""
        try:
            self._fields.clear()
        except AttributeError:
            self._fields = []

    def remove_field(self, index):
        """Removes a field at a specified index.

        If the index is invalid or out of bounds then the error is
        silently swallowed.

        .. note::

            When deleting a field by index, the index of the other fields
            shift to fill the gap just like a regular list.

        Parameters
        -----------
        index: :class:`int`
            The index of the field to remove.
        """
        try:
            del self._fields[index]
        except (AttributeError, IndexError):
            pass

    def set_field_at(self, index, *, name, value, inline=True):
        """Modifies a field to the embed object.

        The index must point to a valid pre-existing field.

        This function returns the class instance to allow for fluent-style
        chaining.

        Parameters
        -----------
        index: :class:`int`
            The index of the field to modify.
        name: :class:`str`
            The name of the field.
        value: :class:`str`
            The value of the field.
        inline: :class:`bool`
            Whether the field should be displayed inline.

        Raises
        -------
        IndexError
            An invalid index was provided.
        """

        try:
            field = self._fields[index]
        except (TypeError, IndexError, AttributeError):
            raise IndexError('field index out of range')

        field['name'] = str(name)
        field['value'] = str(value)
        field['inline'] = inline
        return self

    def to_dict(self):
        """Converts this embed object into a dict."""

        # add in the raw data into the dict
        result = {
            key[1:]: getattr(self, key)
            for key in self.__slots__
            if key[0] == '_' and hasattr(self, key)
        }

        # deal with basic convenience wrappers

        try:
            colour = result.pop('colour')
        except KeyError:
            pass
        else:
            if colour:
                result['color'] = colour.value

        try:
            timestamp = result.pop('timestamp')
        except KeyError:
            pass
        else:
            if timestamp:
                if timestamp.tzinfo:
                    result['timestamp'] = timestamp.astimezone(tz=datetime.timezone.utc).isoformat()
                else:
                    result['timestamp'] = timestamp.replace(tzinfo=datetime.timezone.utc).isoformat()

        # add in the non raw attribute ones
        if self.type:
            result['type'] = self.type

        if self.description:
            result['description'] = self.description

        if self.url:
            result['url'] = self.url

        if self.title:
            result['title'] = self.title

        return result
<|endoftext|>