def main(*args, **kwargs): global bot loop = asyncio.get_event_loop() async def run_bot(): try: await bot.start(*args, **kwargs) finally: if not bot.is_closed(): bot.close() asyncio.ensure_future(run_bot(), loop=loop) try: loop.run_forever() except GatewayNotFound as e: print_exc(f"Gateway wasn't found, likely a Discord API error.", e) except ConnectionClosed as e: print_exc(f"Connection closed by Discord.", e) except LoginFailure as e: print_exc( f"You gave me the wrong credentials, so Discord didn't let me log in.", e) except HTTPException as e: print_exc(f"Some weird HTTP error happened. More details below.", e) except KeyboardInterrupt: pass except Exception as e: print_exc( f"Something else went wrong and I'm not sure what. More details below.", e)
def find_many(self, terms: list[str], by: str) -> list[Subject]: """ Busca por matérias dentro da lista de termos especificada. ### Params - `terms: list[str]`: Uma lista de strings contendo cada termo de pesquisa; - `by: str`: O critério de pesquisa. Dever ser um dos valores: - `'code'`: Busca por código de matéria; - `'name'`: Busca por nome, parcial ou completo; - `'semester'`: Busca por semestre. `terms` deverá ser um `int` ou poder ser convertido para `int`. ### Retorno - Uma lista de instâncias de `Subject` correspondentes a todos os termos especificados, ou uma lista vazia. ### Levanta - `SyntaxError` nos seguintes casos: - `by` não é uma string com um dos valores válidos acima; - Caso `by` seja `'code'` ou `'name'`: - Pelo menos um elemento de `terms` não é uma `str` nem pode ser convertido para `str`. - Caso `by` seja `'semester'`: - Pelo menos um elemento de `terms` não é um `int` nem pode ser convertido para `int`. """ if not isinstance(by, str) or by.lower() not in ('code', 'name', 'semester'): raise SyntaxError( "Argument 'by' is not a string with a valid value") by = by.lower() if by in ('code', 'name'): try: if by == 'code': terms = tuple( [str(term).upper() for term in terms if bool(term)]) else: terms = tuple([str(term) for term in terms if bool(term)]) except Exception: raise SyntaxError( "At least one argument in 'terms' is not a str and cannot be cast to str" ) if by == 'semester': try: terms = tuple([int(term) for term in terms]) except Exception: raise SyntaxError( "At least one argument in 'terms' is not an int and cannot be cast to int" ) try: q: Query = self._session.query(Subject) if by == 'code': q = q.filter(Subject.code.in_(terms)) elif by == 'name': q = q.filter(Subject.fullname.in_(terms)) else: q = q.filter(Subject.semester.in_(terms)) return q.all() except Exception: print_exc() return []
def delete(self, discord_id: int) -> int: """ Exclui um estudante do banco de dados. ### Params - `discord_id: int`: O ID Discord do estudante a ser excluído. ### Retorna - `0`: Operação bem-sucedida; - `1`: Exceção levantada, transação sofreu rollback. ### Levanta - `SyntaxError` caso `discord_id` não for um `int` nem poder ser convertido para `int`. """ try: if not isinstance(discord_id, int): discord_id = int(discord_id) except Exception: raise SyntaxError("Discord ID is not an instance of 'int' and could not be cast to 'int' either") tr = None try: tr = self._session.begin_nested() deleted_student = self._session.query(Student).filter(Student.discord_id == discord_id).first() self._session.delete(deleted_student) self._gcommit(tr) return 0 except Exception: print_exc() if tr is not None: tr.rollback() return 1
def find_all(self) -> list[Subject]: """ Retorna uma lista enorme contendo todas as matérias cadastradas no banco de dados. - AVISO: NÃO tente imprimir essa lista completa em um chat - não só a lista pode exceder o limite de 2000 chars imposto pelo Discord, mas também pode causar muito spam! - Em vez de poluir o seu chat, considere usar o método `find()`. """ # -> this text is left here as a lesson learned kind of thing. how to bypass the orm somewhat and connect to the # DB API on a lower level # This is preferred over the usual session.query(Subject).all() # because the former issues multiple SELECT statements for each id. # Actually nevermind, it was the rollback at the end of every function causing this. # Leaving this here for reference on how to use DBAPI-like querying. # Also note: put a rollback BEFORE every command call that uses the DB. # Not only it avoids the problem mentioned above, it has 2 outcomes: # 1. if there is a current bad transaction in progress, it will be rolled back. # 2. if there are no active transactions, the method is a pass-through. # cnx = engin.connect() # res = [] # for row in cnx.execute(Subject.__table__.select()): # res.append(Subject(id=row['id'], code=row['code'], fullname=row['fullname'], semester=row['semester'])) # return res try: return self._session.query(Subject).all() except Exception: print_exc() return []
async def add_student(self, ctx: commands.Context, *, arguments=''): """ Cadastra o usuário que invocou o comando. - O sistema não permite o cadastro de um usuário já cadastrado. - Sintaxe: `st cadastrar matricula nome completo` - Exemplo: `st cadastrar 2020123456 Celso Souza` """ arguments: list = arguments.split() if len(arguments) < 2 or not str(arguments[0]).isnumeric(): await ctx.send("Sintaxe inválida. Exemplo: `>>st cadastrar 2020123456 Celso Souza`.") return msg: Message = await ctx.send('Cadastrando...') try: statuses = ( "Algo deu errado - cadastro não realizado. Consulte o log para mais detalhes.", "Sintaxe inválida. Exemplo: `>>st cadastrar 2020123456 Celso Souza`.", "Você já está cadastrado." ) result = self.stdao.insert(name=' '.join(arguments[1::]), registry=arguments[0], discord_id=ctx.author.id) if 'err' in result.keys(): await msg.edit(content=statuses[result.get('err') - 1]) else: await msg.edit(content=f'Cadastro realizado com sucesso. ```{smoothen(str(result.get(ctx.author.id)))}```') except Exception: print_exc() await msg.edit(content='Algo deu errado. Consulte o log para detalhes.')
async def cog_command_error(self, ctx: commands.Context, error): if isinstance(error, commands.CheckFailure): await ctx.send( "Você não está cadastrado. Use o comando `>>st cadastrar` para usar os subcomandos de `sc`." ) else: print_exc(f"Exception raised:")
async def find_subject(self, ctx: commands.Context, *, arguments=''): """ Procura uma matéria existente. - Sintaxe: `mt buscar <por> <filtro>`, onde: - `por` pode ser: - 'nome' (busca por nome completo ou parcial); - 'cod' (busca por código exato); - 'sem' (busca por semestre); - Em caso de busca por semestre, `filtro` deve ser um valor numérico inteiro de 0 a 8. - Matérias elo (eletivas) têm semestre 0, mas `mt buscar sem elo` também funciona. - Exemplos: `mt buscar nome banco`, `mt buscar cod est`, `mt buscar sem 4`. """ arguments: list = arguments.split() if len(arguments) < 2 or arguments[0].lower() not in ('nome', 'cod', 'sem'): await ctx.send( "Sintaxe inválida. Exemplos: `>>mt buscar nome banco` ou `>>mt buscar cod bd2`.\nUse `>>help mt buscar` para ajuda." ) else: try: terms = ' '.join(arguments[1::]) if arguments[0].lower() == 'nome': by = 'name' elif arguments[0].lower() == 'cod': by = 'code' else: by = 'semester' terms = 0 if terms.lower() == 'elo' else int(terms) except Exception: await ctx.send( "Sintaxe inválida. Exemplos: `>>mt buscar nome banco` ou `>>mt buscar cod bd2`." ) return msg: Message = await ctx.send('Buscando matéria...') try: matches = self.sbdao.find(terms=terms, by=by, single_result=False) if matches is None: await msg.edit( content="Algo deu errado. Consulte o log para detalhes." ) elif len(matches) == 0: await msg.edit( content= "Nenhuma matéria foi encontrado para esse critério.") else: await msg.edit( content=f"Encontrada(s): ```{smoothen(matches)}```") except Exception: print_exc() await msg.edit( content="Algo deu errado. Consulte o log para detalhes.")
def insert(self, discord_id: int, name: str, registry: int) -> dict: """ Cadastra um estudante novo. Caso o `discord_id` já exista no banco de dados, o estudante é tido como já cadastrado, e nada é feito. ### Params - `discord_id: int`: O ID Discord do estudante a ser cadastrado; - `name: str`: O nome completo do estudante a ser cadastrado; - `registry: int`: O número de matrícula do estudante a ser cadastrado. ### Retorno - Um dict no formato `{std.discord_id: std}` em caso de sucesso. - Casos excepcionais: - `{'err': 1}`: Erro desconhecido, usuário não foi cadastrado; - `{'err': 2}`: Erro de sintaxe nos argumentos passados; - `{'err': 3}`: Usuário já existente. ### Levanta - `SyntaxError` caso os argumentos não possam ser convertidos para seus respectivos tipos. """ try: if not isinstance(discord_id, int): discord_id = int(discord_id) if not isinstance(name, str): name = str(name) if not isinstance(registry, int): registry = int(registry) except Exception: raise SyntaxError("Arguments could not be cast to their intended types") try: # no name or first name only or registry not in 20xxxxxxxx format if len(name) == 0 or ' ' not in name or (len(str(registry)) != 10 and not str(registry).startswith('20')): return {'err': 2} except Exception: print_exc() return {'err': 1} if self.find(discord_id, by='id') is not None: return {'err': 3} else: tr = None try: tr = self._session.begin_nested() new_student = Student(name=name, registry=registry, discord_id=discord_id) self._session.add(new_student) self._gcommit(tr) return {new_student.discord_id: new_student} except Exception: print_exc() if tr is not None: tr.rollback() return {'err': 1}
def update(self, student: Union[int, Student], name=None, registry=None) -> dict: """ Atualiza o cadastro de um estudante. ### Params - `student: int | Student`: O estudante (ou seu ID Discord) a ser cadastrado; - `name: str`: O novo nome do estudante. Se `None`, não vai ser alterado. - `registry: int`: O novo número de matrícula do estudante. Se `None`, não vai ser alterado. - Pelo menos um dentre `name` e `registry` precisa não ser `None`. ### Retorno - Um dict no formato `{std.discord_id: std}` em caso de sucesso. - Casos excepcionais: - `{'err', 1}`: Exceção levantada, transação sofreu rollback; - `{'err', 2}`: Estudante inexistente; - `{'err', 3}`: Nada a modificar (`name` e `registry` ambos são `None`). ### Levanta - `SyntaxError` caso `student` não seja uma instância de `int` nem `Student`, nem possa ser convertido para `int`. """ try: if not isinstance(student, Union[Student, int].__args__): student = int(student) except Exception: raise SyntaxError("Argument 'student' is neither an instance of Student nor int, nor can it be casted to int") if all([name is None, registry is None]): return {'err', 3} tr = None try: tr = self._session.begin_nested() cur_student = self.find(student if isinstance(student, int) else student.id, by='id') if cur_student is None: return {'err', 2} if name is not None: cur_student.name = str(name) if registry is not None: cur_student.registry = int(registry) self._gcommit(tr) return {cur_student.discord_id: cur_student} except Exception: print_exc() if tr is not None: tr.rollback() return {'err': 1}
async def enroll(self, ctx: commands.Context, *, arguments=''): """ Matricula um estudante numa matéria. - Se essa matéria for uma matéria trancada, ela é reativada. - Se essa matéria for uma matéria antiga, ela é atualizada e todos os seus trabalhos redefinidos para o padrão. """ arguments: list = arguments.split() if not arguments: await ctx.send( "Sintaxe inválida. Exemplo: `>>sc matricular mt1 mt2 ...`") else: msg: Message = await ctx.send('Efetuando matrícula...') try: arguments = list(filter(lambda x: len(x) == 3, arguments)) if len(arguments) > 8: arguments = arguments[0:7: 1] # up to 8 at once only, uni rules result = self.scdao.register(student=ctx.author.id, subjects=arguments) if 'err' in result.keys(): if result['err'] == 3: await msg.edit( content= "Matéria(s) inexistente(s).\nUse o comando `>>mt todas` ou `>>mt buscar` para verificar o código da(s) matéria(s) desejada(s)." ) elif result['err'] == 2: await msg.edit( content= "Sintaxe inválida. Exemplo: `>>sc matricular mt1 mt2 ...`" ) else: msg.edit( content= "Algo deu errado. Consulte o log para mais detalhes." ) else: await msg.edit( content= f"Matrícula registrada nas matérias a seguir:\n```{smoothen(tuple([f'{x}: {y}' for x, y in result.items()]))}```" ) except Exception: print_exc() await msg.edit( content='Algo deu errado. Consulte o log para detalhes.')
async def check_student(self, ctx: commands.Context, *, arguments=''): """ Busca os dados de um estudante. - O 'filtro' pode ser uma matrícula (numérica) ou um nome (string). - Sintaxe: `st buscar filtro` - Exemplos: `st buscar 2019123456` ou `st buscar Silva` """ if len(arguments) == 0: await ctx.send("Sintaxe inválida. Exemplo: `>>st buscar 2019123456` (matrícula) ou `>>st buscar João Carlos`.") return q = None comedias = False roger = bool("roger" in arguments.lower()) msg: Message = await ctx.send('Buscando estudantes...') try: if arguments.lower().startswith("comédia"): q = list(self.stdao.find_all()) comedias = True elif arguments.lower() == "todos": q = list(self.stdao.find_all()) elif len(arguments) == 10 and arguments.isnumeric(): q = [self.stdao.find(int(arguments), by='registry')] else: q = list(self.stdao.find(arguments, by='name')) if len(q) == 0: await msg.edit(content="Estudante(s) não encontrado(s).") return else: if comedias: results = "Os comédia dessa porra:" elif roger: results = "O divino meme em charme e osso:" else: results = "Encontrado(s):" await msg.edit(content=f"{results}```{smoothen(q)}```") except Exception: print_exc() await msg.edit(content='Algo deu errado. Consulte o log para detalhes.')
async def lock_enrollment(self, ctx: commands.Context, *, arguments=''): """ Tranca uma, várias ou todas as matérias matriculadas pelo estudante que chamar o comando. - Sintaxe: `sc trancar mt1 mt2 ...` - Exemplo: `sc trancar POO CGR TCP` - `mt1`, `mt2` etc. são códigos de matérias a ser trancadas. Case insensitive, mas precisam necessariamente ter comprimento 3. - Caso `mt1 = 'todas'`, todas as matérias matriculadas são trancadas. """ arguments: list = arguments.split() invalid_syntax = "Sintaxe: `>>sc trancar mt1 mt2 ...` - caso `mt1 = 'todas'`, todas as matérias ativas do estudante serão trancadas." if not arguments: await ctx.send(invalid_syntax) else: msg: Message = await ctx.send('Trancando matrículas...') try: if arguments[0] == 'todas': lock_all = True else: lock_all = False result = self.scdao.lock(ctx.author.id, arguments, lock_all=lock_all) msgs = ("Você trancou todas as suas matrículas.", "Algo deu errado. Consulte o log para mais detalhes.", "Nenhuma matéria válida fornecida. Nada foi trancado.") if 'err' not in result.keys(): if not lock_all: await msg.edit( content= f"Matrículas trancadas com sucesso: ```{smoothen([f'-> {x}' for x in result.get(0)])}```" ) else: await msg.edit(content=msgs[0]) else: await msg.edit(content=msgs[result.get('err')]) except Exception: print_exc() await msg.edit( content='Algo deu errado. Consulte o log para detalhes.')
async def edit_student(self, ctx: commands.Context, *, arguments=''): """ Edita o cadastro do usuário que invocou o comando. - Sintaxe: `editar campo novo valor`, onde campo pode ser: - `nome`: Edita o nome do estudante; - `mtr`: Edita a matrícula do estudante. - Exemplos: `st editar nome Carlos Eduardo` ou `st editar mtr 2020048596` """ arguments = arguments.split() if len(arguments) < 2: await ctx.send("Sintaxe inválida. Exemplo: `>>st editar mtr 2019246852` ou `>>st editar nome Carlos Eduardo`.") return msg: Message = await ctx.send('Editando...') try: if str(arguments[0]).lower() == 'nome': await msg.edit(content='Editando nome...') result = self.stdao.update(ctx.author.id, name=' '.join(arguments[1::])) elif str(arguments[0]).lower() == 'mtr': await msg.edit(content='Editando matrícula...') result = self.stdao.update(ctx.author.id, registry=int(arguments[1])) else: await msg.edit(content="Campo inválido - o campo pode ser `nome` ou `mtr`.") return statuses = ( 'Algo deu errado. Consule o log para detalhes.', 'Você não está cadastrado.', 'Nenhuma modificação a fazer.' ) if 'err' in result.keys(): await msg.edit(content=statuses[result.get('err') - 1]) else: await msg.edit(content=f'Dados alterados. ```{smoothen(str(result.get(ctx.author.id)))}```') except Exception as e: await msg.edit(content='Algo deu errado. Consule o log para detalhes.') print_exc()
def find_enrollments(self, std_id: int, previous=False, active=True) -> list[Registered]: """ Busca todas as matrículas correspondentes a um ID de estudante. ### Params - `std_id: int`: O ID Discord do estudante cujas matrículas vão ser retornadas; - `previous: bool`: Quando `True`, retorna matérias de semestres anteriores, senão, somente matérias do semestre atual; - `active: bool`: Quando `False`, retorna também matérias trancadas, senão, somente matérias ativas. ### Retorno - Uma `list`a com instâncias de `Registered` para todos os resultados encontrados. - Se nenhum for encontrado, ou se alguma exceção for lançada durante a execução, retorna uma lista vazia. ### Levanta - `SyntaxError` caso `std_id` não for uma instância de `int`, nem puder ser convertido para `int`. - Isso inclui o caso de `std_id` ser `None`. """ if not isinstance(std_id, int): try: std_id = int(std_id) except Exception: raise SyntaxError( "Student ID is not an instance of 'int' and cannot be cast to 'int'" ) try: q = self._session.query(Registered).filter( Registered.std_id == std_id) if active: q = q.filter(Registered.active == 'true') if previous: q = q.filter(Registered.semester <= self.cur_semester) else: q = q.filter(Registered.semester == self.cur_semester) return q.all() except Exception as e: print_exc() return []
async def roger_foto(self, ctx: commands.Context): """Você perguntou? O Roger aparece!""" msg: Message = await ctx.send("Invocando o Roger...") try: roger_img = self._fetch_roger_image() embed = Embed(description=roger_img[0], colour=Colour(randint(0x000000, 0xFFFFFF))) embed.set_image(url=roger_img[1]) if roger_img[0].lower() == "julio_cobra": cobra = True ct = 'Cacilda, agora a cobra fumou. Você tirou o julio_cobra.' else: cobra = False ct = None await msg.edit(content=ct, embed=embed) if cobra and ctx.guild.id == 567817989806882818: await self._aprisionar(ctx) except Exception as e: await msg.edit("Ih, deu zica.") print_exc("Zica thrown:")
async def add_subject(self, ctx: commands.Context, *, arguments=''): """ Adiciona uma matéria nova. - Sintaxe: `mt add CDE SM Nome da Matéria`, onde: - `CDE`: Código, uma sigla de precisamente 3 letras, única entre todas as matérias registradas. - `SM`: Semestre, um número inteiro de 0-8 indicando o semestre. - Semestre 0 significa que a matéria é eletiva (elo). - Exemplo: `mt add BD2 4 Banco de Dados II` """ arguments = arguments.split() if len(arguments) <= 2: await ctx.send( "Sintaxe inválida. Exemplo: `>>mt add BD2 4 Banco de Dados II`." ) else: msg: Message = await ctx.send('Adicionando matéria...') try: statuses = ( "Algo deu errado. Consulte o log para detalhes.", "Sintaxe inválida. Exemplo: `>>mt add BD2 4 Banco de Dados II`.", "O código especificado para a matéria já existe.") result = self.sbdao.insert(code=arguments[0], fullname=' '.join(arguments[2::]), semester=abs(int(arguments[1]))) if 'err' in result.keys(): await msg.edit(content=statuses[result.get('err') - 1]) else: await msg.edit( content= f"Matéria adicionada com sucesso.\n```{tuple(result.values())[0]}```" ) except Exception: print_exc() await msg.edit( content="Algo deu errado. Consulte o log para detalhes.")
def update(self, code: Union[str, Subject], newcode=None, fullname=None, semester=None) -> dict: """ Atualiza as informações de uma matéria. ### Params - `code: Subject | str`: A matéria (ou o código dela) a ser atualizada; - `newcode: str`: O novo código; - `fullname: str`: O novo nome completo; - `semester: int`: O novo semestre. - Pelo menos um dos 3 acima não deve ser `None`. A matéria permanecerá inalterada nos atributos que forem `None`. ### Retorno - Um dict no formato `{0: sbj}` em caso de sucesso. - Casos excepcionais: - `{'err': 1}`: Exceção lançada, transação sofreu rollback; - `{'err': 2}`: Erro de sintaxe nos argumentos passados: - `newcode` ou `fullname` é uma string vazia; - `semester` não é um `int` dentro do intervalo [0, 11[ - `{'err': 3}`: Matéria inexistente. ### Levanta - `SyntaxError` caso `code` não seja uma instância de `str` nem `Subject`, nem possa ser convertido para `str`. """ if not isinstance(code, Union[str, Subject].__args__): try: code = str(code) except Exception: raise SyntaxError( "Argument 'code' is not an instance of 'str' nor 'Subject', nor can it be cast to 'str'" ) try: if any([ all([newcode is None, fullname is None, semester is None]), any([ newcode is not None and len(str(newcode)) != 3, fullname is not None and len(fullname) < 3, semester is not None and int(semester) not in range(0, 11) ]) ]): return {'err': 2} except Exception: print_exc() return {'err': 2} tr = None try: cur_sbj = self.find( code.upper() if isinstance(code, str) else code.id, by='code') if cur_sbj is None: return {'err': 3} else: tr = self._session.begin_nested() if newcode is not None: cur_sbj.code = str(newcode).upper() if fullname is not None: cur_sbj.fullname = str(fullname) if semester is not None: cur_sbj.semester = int(semester) self._gcommit(tr) return {0: cur_sbj} except Exception: print_exc() if tr is not None: tr.rollback() return {'err': 1}
async def cog_command_error(self, ctx: commands.Context, error): if isinstance(error, commands.NotOwner): await ctx.send( "Somente o proprietário do bot pode usar esse comando.") else: print_exc()
async def edit_subject(self, ctx: commands.Context, *, arguments=''): """ Edita uma matéria em específico. - Sintaxe: `mt editar <campo> <código> <novo valor>`, onde: - `código`: Código da matéria; - `campo`: Campo a editar (`nome`, `cod`, `sem` ou `todos`); - `novo valor`: Novo valor; - No caso de `campo == 'todos'`, a ordem dos novos valores é `<código> <semestre> <nome completo>`. - Exemplos: `mt editar cod AL1 ALG`, `mt editar todos AL1 ALG 0 Algoritmos 1`. """ arguments: list = arguments.split() syntax_error = "Sintaxe inválida. Exemplos: `mt editar cod AL1 ALG`, `mt editar todos AL1 ALG 0 Algoritmos 1`." if len(arguments) < 3 or any([ arguments[0].lower() not in ('nome', 'cod', 'sem', 'todos'), len(arguments[1]) != 3 ]): await ctx.send(syntax_error) else: msg: Message = await ctx.send('Editando matéria...') err_msgs = ("Algo deu errado. Consulte o log para detalhes.", syntax_error, "A matéria informada não existe.") try: field = arguments[0].lower() code = arguments[1].upper() if field == 'todos': new_value = arguments[2::] if len(new_value) < 3: raise SyntaxError( '(SubjectController.update) Number of arguments passed is lower than minimum for option \'todos\'' ) elif field == 'cod': new_value = arguments[2].upper() elif field == 'sem': new_value = int(arguments[2]) else: new_value = ' '.join(arguments[2::]) except Exception: await msg.edit(content=syntax_error + '\nPara mais detalhes, use `>>help mt editar`.') return try: update_kwargs = {'code': code} if field == 'todos': update_kwargs['newcode'] = new_value[0] update_kwargs['semester'] = new_value[1] update_kwargs['fullname'] = ' '.join(new_value[2::]) else: update_kwargs[ 'newcode'] = new_value if field == 'cod' else None update_kwargs[ 'semester'] = new_value if field == 'sem' else None update_kwargs[ 'fullname'] = new_value if field == 'nome' else None result = self.sbdao.update(**update_kwargs) if 'err' in result.keys(): await msg.edit(content=err_msgs[result.get('err') - 1]) else: await msg.edit( content= f'Matéria editada: ```{smoothen(str(result.get(0)))}```' ) except Exception: print_exc() await msg.edit( content="Algo deu errado. Consulte o log para detalhes.")
def find(self, terms: Union[int, str], by: str, single_result=True) -> Union[list[Subject], Subject, None]: """ Busca uma matéria baseado num filtro. ### Params - `terms: str | int`: Os termos de pesquisa; - `single_result: bool`: Caso `True`, retorna o primeiro resultado ou None. Senão, retorna uma lista com todos os resultados. Opcional. - `by: str`: O critério de pesquisa. Deve ser um dos valores: - `'code'`: Busca por código de matéria; - `'name'`: Busca por nome, parcial ou completo; - `'semester'`: Busca por semestre. `terms` deverá ser um `int` ou poder ser convertido para `int`. ### Retorno - Caso `single_result == True`, retorna uma instância de `Subject`, ou `None` se nada for encontrado; - Senão, retorna uma lista de instâncias de `Subject`, ou uma lista vazia. - Em ambos os casos, retorna `None` se uma exceção for lançada. ### Levanta - `SyntaxError` nos seguintes casos: - `by` não é uma string com um dos valores válidos acima; - Caso `by` seja `'code'` ou `'name'`: - `terms` não é uma `str` nem pode ser convertido para `str`. - Caso `by` seja `'semester'`: - `terms` não é um `int` nem pode ser convertido para `int`. """ if not isinstance(by, str) or by.lower() not in ('code', 'name', 'semester'): raise SyntaxError( "Argument 'by' is not a string with a valid value") if by in ('code', 'name') and not isinstance(terms, str): try: terms = str(terms) except Exception: raise SyntaxError( "Argument 'terms' is not a str and cannot be cast to str") if by == 'semester' and not isinstance(terms, int): try: terms = int(terms) except Exception: raise SyntaxError( "Argument 'terms' is not an int and cannot be cast to int") try: q: Query = self._session.query(Subject) if by == 'code': q = q.filter(Subject.code == terms.upper()) elif by == 'name': q = q.filter(Subject.fullname.ilike(f'%{terms}%')) else: q = q.filter(Subject.semester == terms) if single_result: return q.first() else: return q.all() except Exception: print_exc() return None
async def update_grade(self, ctx: commands.Context, *, arguments=''): """ Atualiza a nota de uma matéria em específico. - O comando rejeita trabalhos pendentes - somente trabalhos com status `OK` são alterados! - Sintaxe: `sc nota codigo trabalho novanota [antigo]` - Se `antigo = 'sim'` então o comando altera a nota de uma matéria de um semestre anterior, ou de alguma matéria trancada. - Senão, o comando só vai alterar notas de matérias ativas do semestre atual. - Exemplo: `sc nota AL1 AV1 9.5 sim` """ arguments: list = arguments.split() invalid_syntax = "Sintaxe: `>>sc nota codigo trabalho novanota antigo?` - 'antigo' é opcional: sim ou não.\nExemplo: `>>sc nota AL1 AV1 9.5`" invalid_syntax += "\nSe tentar mudar a nota para uma matéria de um semestre anterior, terá que especificar 'sim' em antigo." if len(arguments) < 3: await ctx.send(invalid_syntax) else: exam_grades = { 'AV1': [8.0, 1], 'APS1': [2.0, 2], 'AV2': [8.0, 3], 'APS2': [2.0, 4], 'AV3': [10.0, 5] } msg: Message = await ctx.send('Atualizando nota...') try: if len(arguments) > 4: arguments = arguments[0:3:1] if len(arguments) == 4: current = not bool(arguments[3].lower() == 'sim') else: current = True sbj_code = arguments[0].upper() assignment = arguments[1].upper() grade = abs(float(arguments[2])) if exam_grades.get(assignment) is None: await msg.edit( content=f'*Trabalho inválido!*\n{invalid_syntax}') return if grade > exam_grades.get(assignment)[0]: await msg.edit( content= f"*A nota especificada é mais alta que o permitido:* `{exam_grades.get(assignment)[0]}`" ) return result = self.scdao.update( student=ctx.author.id, subject=sbj_code, exam_type=exam_grades.get(assignment)[1], newval=grade, grade=True, current=current, active=current) err_responses = ( "Algo deu errado. Consulte o log para mais detalhes.", invalid_syntax, "O trabalho para a matéria especificada não foi encontrado.", "Você não pode alterar a nota para um trabalho que não foi entregue ainda." ) if 'err' in result.keys(): await msg.edit(content=err_responses[result.get('err') - 1] ) else: newmsg = f"Nota alterada: ```{smoothen(f'{tuple(result.keys())[0]} | {assignment}: {nround(grade, 1)}')}```" await msg.edit(content=newmsg) except Exception: print_exc() await msg.edit( content='Algo deu errado. Consulte o log para detalhes.')
async def russian_roulette(self, ctx: commands.Context, *, arguments): """ Roleta russa com N opções. Se o trigger cair no meio delas, você vai morrer por (opções / 2) minutos. 6 opções por padrão.\n - Sintaxe: `rr opções`\n - Exemplo: `rr 10`\n P.S. Não chame o bot de diabo e mande ele morrer ao mesmo tempo, ele fica nervoso. """ arguments = arguments.split() options = 6 if len(arguments) > 0 and str( arguments[0]).isnumeric() and int(arguments[0]) > 1: options = abs(int(arguments[0])) trigger = randint(1, options) morre_diabo = False if len(arguments) >= 2: morre_diabo = 'morre' in arguments[0].lower( ) and 'diabo' in arguments[1].lower() dprint(f"Roleta para {' '.join(arguments)}: {options} opções.") if trigger == options // 2 or morre_diabo: original_roles = [ x for x in ctx.author.roles if x.name != '@everyone' ] if not morre_diabo: await ctx.send(u'<:ikillu:700684891251277904> \U0001F4A5') else: await ctx.send( u"<:morrediabo:779864127249055795><:ikillu:700684891251277904> \U0001F4A5" ) try: respawn = (options // 2) * 60 response = None if morre_diabo: response = "ENTÃO MORRE, DIABO!" dprint(f"O DIABO {str(ctx.author.name).upper()} MORREU!!") else: response = f"{ctx.author.mention} morreu! Respawn em {respawn} segundos..." for role in original_roles: await ctx.author.remove_roles(role) await ctx.send(response) await ctx.author.add_roles( ctx.guild.get_role(778774271869583480)) await sleep(respawn) try: await ctx.author.remove_roles( ctx.guild.get_role(778774271869583480)) for role in original_roles: await ctx.author.add_roles(role) if not morre_diabo: await ctx.send(f"{ctx.author.mention}: Levante e ande!" ) else: await ctx.send( f"{ctx.author.mention}: EU QUERO QUE VOCÊ SE FODA, SEU FILHO DE UMA PUTA! <:morrediabo:779864127249055795>" ) except Exception as e: if 'unknown member' in str(e).lower(): await ctx.send( f"Parece que {ctx.author.name} morreu de vez...") else: print_exc() except Exception as e: if 'missing permisisons' in str(e).lower(): await ctx.send( f"A arma atirou, mas parece que {ctx.author.name} é imortal..." ) else: print_exc() else: await ctx.send(u'<:ikillu:700684891251277904> \U0001F389') await ctx.send( f"{ctx.author.mention} deu sorte! A bala era de mentira!")
async def view_self(self, ctx: commands.Context, *, arguments=''): """ Verifica se a pessoa que invocou o comando está cadastrada no banco de dados. - Se sim, então os dados da pessoa são mostrados, juntamente com suas provas e respectivos status. - Aceita um argumento, sendo este o tipo de exibição das matérias: - `completo`: mostra trabalhos como `TRB: STS (nota)` - `notas`: mostra trabalhos como `TRB: nota` - `médias` ou `medias`: ao invés de trabalhos, mostra a média atual da matéria. - Caso a AV2 já tenha sido marcada como OK, mostra também um status "aprovado" ou "reprovado", dependendo da média. - Nada ou qualquer outra coisa além dos já citados: mostra trabalhos como o padrão, `TRB: STS` - Sintaxe: `st ver exibicao` - Exemplo: `st ver notas` """ msg: Message = await ctx.send('Buscando...') cur_student: Student = self.stdao.find(ctx.author.id, by='id') if cur_student is None: await msg.edit(content="Você não está cadastrado.") else: start = time() command = next(iter(arguments.split()), None) if command is not None: command = command.lower() try: enrollments = list(self.scdao.find_enrollments(cur_student.id)) cur_strings = [] # parse to strings afterward, if applicable if enrollments: semester_avg = [] # insert class averages here for registry in enrollments: composite = str(registry.subject.code) + ' | ' # MT1 | AV1: STS (10.0) | APS1: STS (10.0) | AV2: STS (10.0) | APS2: STS (10.0) | AV3: STS (10.0) if command == 'completo': composite += ' | '.join([f"{exam.show_type()}: {exam.show_status()} ({exam.show_grade()})" for exam in registry.exams]) # MT1 | AV1: 10.0 | APS1: 10.0 | AV2: 10.0 | APS2: 10.0 | AV3: 10.0 elif command == 'notas': composite += ' | '.join([f"{exam.show_type()}: {exam.show_grade()}" for exam in registry.exams]) # MT1 | Média: 10.0 | Status: Aprovado (se AV2 OK) elif command == 'médias' or command == 'medias': average = uni_avg(*[exam.grade for exam in registry.exams]) semester_avg.append(average) composite += ' | '.join([f"Média: {average}"]) for exam in registry.exams: if exam.exam_type == 2 and exam.status == 1: composite += f" | Status: {'Aprovado' if average >= 7.0 else 'Reprovado'}" break # MT1 | AV1: STS | APS1: STS | AV2: STS | APS2: STS | AV3: STS else: composite += ' | '.join([f"{exam.show_type()}: {exam.show_status()}" for exam in registry.exams]) cur_strings.append(composite) enrollments.clear() # memory cleanup if command == 'médias' or command == 'medias': semester_avg = nround(avg(semester_avg), 2) savg_msg = f"CR do semestre: {semester_avg}" cur_strings.extend(('---', savg_msg)) final_msg = [str(cur_student), '---'] if cur_strings: final_msg.extend(cur_strings) else: final_msg.append('-- aluno não matriculado em nenhuma matéria --') final_msg = tuple(final_msg) except Exception as e: final_msg = 'Algo deu errado. Consulte o log para detalhes.' print_exc() else: final_msg = f"Seus dados: ```{smoothen(final_msg)}```" finally: end = round(time() - start, 2) await msg.edit(content=final_msg + f"Tempo de execução: {end} seg.") dprint(f"------------------ Time taken: {end} sec ------------------")
def update(self, student: Union[Student, int], subject: Union[Subject, str], exam_type: int, newval, grade: bool, current=True, active=True) -> dict: """ Atualiza um dado em um trabalho. ### Params - `student: Student | int`: O estudante (ou o ID Discord dele) cujo trabalho deverá ser atualizado; - `subject: Subject | str`: A matéria (ou o código dela) cujo trabalho deverá ser atualizado; - `newval`: O novo valor do status ou da nota do trabalho/prova; - `grade: bool`: Caso `True`, atualiza a nota do trabalho/prova com `newval`, senão, atualiza o status. - `current: bool`: Caso `True`, busca entre provas do semestre atual somente; - `active: bool`: Caso `True`, busca entre provas somente em matérias não trancadas; - `exam_type: int`: O tipo da prova ou trabalho a ser pesquisado(a): - `1`: Prova AV1 - `2`: Trabalho APS1 - `3`: Prova AV2 - `4`: Trabalho APS2 - `5`: Prova AV3 ### Retorno - Um dict no formato `{sbj.fullname: 0}` no caso de sucesso. - Casos excepcionais: - `{'err': 1}`: Exceção lançada, transação sofreu rollback; - `{'err': 2}`: Erro de sintaxe nos argumentos passados. - `{'err': 3}`: Trabalho não encontrado. - `{'err': 4}`: (Atualização de nota) Trabalho ainda não entregue. Não se atualiza notas de trabalhos não entregues. ### Levanta - `SyntaxError` nos casos a seguir: - `student` não é uma instância de `Student` nem `int` e não pode ser convertido para `int`; - `subject` não é uma instância de `Subject` nem `str` e não pode ser convertido para `str`; - `exam_type` não é um `int` entre 1 e 5. """ if not isinstance(student, Union[Student, int].__args__): try: student = int(student) except Exception: raise SyntaxError( "Argument 'student' not an instance of Student nor int, cannot be cast to int" ) if not isinstance(subject, Union[Subject, str].__args__): try: subject = str(subject) except Exception: raise SyntaxError( "Argument 'subject' not an instance of Subject nor str, cannot be cast to str" ) if not isinstance(exam_type, int): try: exam_type = int(exam_type) except Exception: raise SyntaxError( "Argument 'exam_type' is not an instance of int, nor can it be cast to int" ) if exam_type not in range(1, 6): raise SyntaxError( "Argument 'exam_type' is not an integer between 1 and 5") tr = None try: tr = self._session.begin_nested() if isinstance(student, int): student = self._session.query(Student).filter( Student.discord_id == student).first() if isinstance(subject, str): subject = self._session.query(Subject).filter( Subject.code == subject.upper()).first() exam = self.find_exam(student=student, subject=subject, exam_type=exam_type, current=current, active=active) if exam is None: return {'err': 3} else: if grade: if int(exam.status) != 1: tr.rollback() return {'err': 4} else: exam.grade = nround(newval, 1) else: if isinstance(newval, str): exam.status = ('OK', 'EPN', 'PND').index(newval - 1) elif isinstance(newval, int): exam.status = newval else: tr.rollback() return {'err': 2} self._gcommit(tr) return {subject.fullname: 0} except Exception: print_exc() if tr is not None: tr.rollback() return {'err': 1}
async def update_status(self, ctx: commands.Context, *, arguments=''): """ Altera o status de um trabalho. Também reconhecido como o comando `sts`. - Sintaxe: `sc status codigo_materia trabalho novostatus`; onde `novostatus` pode ser: - OK; - PND (pendente); - EPN (envio/entrega pendente). - Exemplo: `sc status AL1 AV1 OK` """ arguments: list = arguments.split() invalid_syntax = "Sintaxe: `>>sc status codigo trabalho novostatus`.\nExemplo: `>>sc status AL1 AV1 OK`" if len(arguments) < 3: await ctx.send(invalid_syntax) else: msg: Message = await ctx.send('Atualizando status...') statuses = ('OK', 'EPN', 'PND') exam_types = ('AV1', 'APS1', 'AV2', 'APS2', 'AV3') sbj_code = arguments[0].upper() assignment = arguments[1].upper() status = arguments[2].upper() if assignment not in exam_types: await msg.edit(content=invalid_syntax) return else: assignment = exam_types.index(assignment) + 1 if status not in statuses: await msg.edit(content=invalid_syntax) return if len(arguments) >= 4 and ' '.join( arguments[2:4:]).lower() in ('envio pendente', 'entrega pendente'): status = 2 elif status == 'pendente': status = 3 else: status = statuses.index(status) + 1 try: result = self.scdao.update(student=ctx.author.id, subject=sbj_code, exam_type=assignment, newval=status, grade=False) err_responses = ( "Algo deu errado. Consulte o log para mais detalhes.", invalid_syntax, "O trabalho para a matéria especificada não foi encontrado." ) if 'err' in result.keys(): await msg.edit(content=err_responses[result.get('err') - 1] ) else: newmsg = f"Status alterado: ```{smoothen(f'{tuple(result.keys())[0]} | {exam_types[assignment - 1]}: {statuses[status - 1]}')}```" await msg.edit(content=newmsg) except Exception: print_exc() await msg.edit( content='Algo deu errado. Consulte o log para detalhes.')
def register(self, student: Union[Student, int], subjects: Union[list[str], tuple[str], set[str]]) -> dict: """ Matricula um estudante em uma ou várias matérias.\n Se a matéria for trancada, ela é reativada. Se a matéria for de um semestre anterior, todos os seus trabalhos são redefinidos. ### Params - `student: Student | int`: O estudante (ou seu ID Discord) ingressando nas matérias especificadas; - `subjects: list | tuple | set`: Um iterável contendo os códigos (todos `str`) das matérias a serem registradas. ### Retorno - Um dict com o formato `sbj.code: sbj.fullname` para cada item. - Casos excepcionais: - `{'err': 1}`: Exceção lançada, transação sofreu rollback; - `{'err': 2}`: Erro de sintaxe nos argumentos passados: - Estudante nulo ou inexistente; - Lista de matérias vazia - `{'err': 3}`: Lista de matérias válidas vazia, nada a matricular. - Isso é diferente do erro 2, onde o argumento `subjects` é vazio ou nulo. No erro 3, nenhuma matéria com os códigos informados foi encontrada. ### Levanta - `SyntaxError` nos seguintes casos: - Caso `student` não seja uma instância de `Student` ou `int`, nem possa ser convertido para `int`; - Caso os valores de `subject` não sejam todos `str`s nem possam ser convertidas para tal. """ if not all([bool(student), bool(subjects)]): return {'err': 2} try: if not isinstance(student, Union[Student, int].__args__): student = int(student) except Exception: raise SyntaxError( "Argument 'student' not an instance of Student nor int, cannot be cast to int" ) try: subjects = [str(x).upper() for x in subjects if bool(x)] except Exception: raise SyntaxError( "At least one object in argument 'subjects' is not a string and cannot be cast to string" ) tr = None try: student = self._session.query(Student).filter( Student.discord_id == (student if isinstance(student, int) else student.id)).first() if student is None or len(subjects) == 0: return {'err': 2} tr = self._session.begin_nested() modified_subjects = dict({}) enrollments = self.find_enrollments(student.id, previous=True, active=False) for reg in enrollments: if reg.subject.code in subjects: modified_subjects[reg.subject.code] = reg.subject.fullname while reg.subject.code in subjects: subjects.remove(reg.subject.code) if not reg.active: # reactivating a locked subject reg.active = True if reg.semester != self.cur_semester: # retrying a failed subject reg.semester = self.cur_semester for exam in reg.exams: exam.reset() # no need to add reg to session here, it already is in the session loaded_subjects = self._session.query(Subject).filter( Subject.code.in_(subjects)).all() if not loaded_subjects and not modified_subjects: return {'err': 3} for subj in loaded_subjects: registry = Registered(semester=self.cur_semester, active=True) registry.subject = subj registry.student = student for x in range(1, 6): exam = Exam(exam_type=x, status=3, grade=0.0) exam.registry = registry registry.exams.append(exam) student.registered_on.append(registry) modified_subjects[subj.code] = subj.fullname self._gcommit(tr) self._session.expire_all() return modified_subjects except Exception: print_exc() if tr is not None: tr.rollback() return {'err': 1}
def find_exam(self, student: Union[Student, int], subject: Union[Subject, str], exam_type: int, current=True, active=True) -> Union[Exam, None]: """ Busca uma prova em específico, para um estudante e matéria em específico. ### Params - `student: int | Student`: O estudante (ou o ID Discord dele) cuja prova deve ser pesquisada; - `subject: str | Subject`: A matéria (ou o código dela) cuja prova deve ser pesquisada; - `current: bool`: Caso `True`, busca provas do semestre atual somente; - `active: bool`: Caso `True`, busca provas somente em matérias não trancadas; - `exam_type: int`: O tipo da prova ou trabalho a ser pesquisado(a): - `1`: Prova AV1 - `2`: Trabalho APS1 - `3`: Prova AV2 - `4`: Trabalho APS2 - `5`: Prova AV3 - Por padrão, `current == True` e `active == True`. Isso realiza uma pesquisa entre as matérias ativas do semestre atual. ### Retorno - A primeira instância de `Exam` encontrada, ou `None` caso uma exceção for lançada, ou nada for encontrado. ### Levanta - `SyntaxError` nos seguintes casos: - `student` não é uma instância de `Student` nem `int` e não pode ser convertido para `int`; - `subject` não é uma instância de `Subject` nem `str` e não pode ser convertido para `str`; - `exam_type` não é um `int` entre 1 e 5. """ if not isinstance(student, Union[Student, int].__args__): try: student = int(student) except Exception: raise SyntaxError( "Argument 'student' not an instance of Student nor int, cannot be cast to int" ) if not isinstance(subject, Union[Subject, str].__args__): try: subject = str(subject) except Exception: raise SyntaxError( "Argument 'subject' not an instance of Subject nor str, cannot be cast to str" ) if not isinstance(exam_type, int): try: exam_type = int(exam_type) except Exception: raise SyntaxError( "Argument 'exam_type' is not an instance of int, nor can it be cast to int" ) if exam_type not in range(1, 6): raise SyntaxError( "Argument 'exam_type' is not an integer between 1 and 5") try: if isinstance(student, int): student = self._session.query(Student).filter( Student.discord_id == student).first() if isinstance(subject, str): subject = self._session.query(Subject).filter( Subject.code == subject.upper()).first() q: Query = self._session.query(Exam).join(Registered, Registered.id == Exam.id) q = q.filter(Registered.std_id == student.id, Registered.sbj_id == subject.id, Exam.exam_type == exam_type) if current: q = q.filter(Registered.semester == self.cur_semester) if active: q = q.filter(Registered.active == 'true') return q.first() except Exception: print_exc() return None
async def cog_command_error(self, ctx: commands.Context, error): if isinstance(error, commands.CommandOnCooldown): await ctx.send("Calma, rapaz. Sem spam.") else: print_exc()
def lock(self, student: Union[Student, int], subjects: Union[list[str], tuple[str], set[str]], lock_all: bool = False) -> dict: """ Tranca uma, várias ou todas as matrículas de um estudante. ### Params - `student: Student | int`: O estudante (ou o ID Discord dele) cuja(s) matéria(s) deverá(ão) ser trancada(s); - `subjects: list[str]`: Um iterável contendo o(s) código(s) da(s) matéria(s) a ser(em) trancada(s); - `lock_all: bool`: Caso `True`, tranca todas as matérias do estudante. ### Retorno - Um dict no formato `{0: [sbj1.fullname, sbj2.fullname, ...]}` em caso de sucesso. - Casos excepcionais: - `{'err': 1}`: Exceção lançada, transação sofreu rollback; - `{'err': 2}`: Nada a trancar. ### Levanta - `SyntaxError` nos casos a seguir: - `student` não é uma instância de `Student` nem `int` e não pode ser convertido para `int`; - `subjects` não é uma instância de uma lista, tupla ou conjunto; - Pelo menos um elemento de `subject` não é uma `str` nem pode ser convertido para `str`. """ if not isinstance(student, Union[Student, int].__args__): try: student = int(student) except Exception: raise SyntaxError( "Argument 'student' not an instance of Student nor int, cannot be cast to int" ) if not isinstance(subjects, Union[list, tuple, set].__args__): raise SyntaxError( "Argument 'subjects' not an instance of list, tuple or set") try: subjects = tuple([str(x).upper() for x in subjects]) except Exception: raise SyntaxError( "At least one object in argument 'subjects' is not a string and cannot be cast to string" ) if not (subjects or lock_all): return {'err': 2} tr = None try: print( f'Calling scdao.lock with student {student} | subjects {subjects}' ) tr = self._session.begin_nested() changed = False if isinstance(student, int): student = self._session.query(Student).filter( Student.discord_id == student).first() eager_enrollments = self.find_enrollments(student.id) modified_enrollments = [] for reg in eager_enrollments: print(f"Current registry's subject code: {reg.subject.code}") if lock_all or reg.subject.code in subjects: changed = True reg.active = False modified_enrollments.append(reg.subject.fullname) self._gcommit(tr) return {0: modified_enrollments} if changed else {'err': 2} except Exception: print_exc() if tr is not None: tr.rollback() return {'err': 1}
def insert(self, code: str, fullname: str, semester: int) -> dict: """ Cadastra uma matéria nova no banco de dados. ### Params - `code: str`: O código da matéria. Deverá ser uma string full uppercase de 3 chars; - `fullname: str`: O nome completo da matéria; - `semester: int`: O semestre a qual a matéria pertence. - Matérias eletivas são de semestre 0. ### Retorno - Um dict no formato `{sbj.code: sbj}` em caso de sucesso. - Casos excepcionais: - `{'err': 1}`: Exceção lançada, transação sofreu rollback; - `{'err': 2}`: Erro de sintaxe nos argumentos passados; - `{'err': 3}`: O código especificado já existe. ### Levanta - `SyntaxError` nos seguintes casos: - `code` ou `fullname` não são `str`, nem podem ser convertidas para `str`; - `semester` não é um `int` e nem pode ser convertido para `int`. """ if not isinstance(code, str): try: code = str(code) except Exception: raise SyntaxError( "Argument 'code' is not a string nor can it be converted to string" ) if not isinstance(fullname, str): try: fullname = str(fullname) except Exception: raise SyntaxError( "Argument 'fullname' is not a string nor can it be converted to string" ) if not isinstance(semester, int): try: semester = int(semester) except Exception: raise SyntaxError( "Argument 'semester' is not an int nor can it be converted to int" ) if any( [len(code) != 3, len(fullname) == 0, semester not in range(0, 11)]): return {'err': 2} else: tr = None try: if self.find(terms=code.upper(), by='code') is not None: return {'err': 3} else: tr = self._session.begin_nested() new_subject = Subject(code=code.upper(), fullname=fullname, semester=abs(semester)) self._session.add(new_subject) self._gcommit(tr) return {new_subject.code: new_subject} except Exception: print_exc() if tr is not None: tr.rollback() return {'err': 1}