School Commit Init

This commit is contained in:
2024-08-31 12:07:21 +03:00
commit 0b130ee18c
2801 changed files with 4720552 additions and 0 deletions
@@ -0,0 +1,129 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
@@ -0,0 +1,53 @@
# 💻 Assignment 07
## Requirements
- You will be given one of the problems below to solve
- The program must provide a menu-driven console user interface.
- Use classes to represent the following:
- The `UI` class which implements the user interface
- A `Services` class that implements the required functionalities
- The domain entity (`complex`, `expense`, `student`, `book`) class
- A `Repository` class whose job it is to store the domain entity instances. This class will have three implementations: (1) a `MemoryRepository`, which stores domain instances in memory (just like in previous assignments); (2) a `TextFileRepository`, which persists domain entities to a text file, and (3) a `BinaryFileRepository`, which persists domain entities using object serialization with [Pickle](https://docs.python.org/3.10/library/pickle.html). You should be able to switch between which repository is in use by changing the instantiated class. When using a file-backed repository, changes made while running the program must be persisted across runs.
- Have 10 programmatically generated entries in the application at start-up. Save these to the text/binary input files.
- Unit tests and specifications for non-UI functions related to the first functionality.
## Problem Statements
### 1. Complex numbers
Manage a list of complex numbers in `a+bi` form and provide the user the following features:
1. Add a number. The number is read from the console.
2. Display the list of numbers.
3. Filter the list so that it contains only the numbers between indices `start` and `end`, where these values are read from the console.
4. Undo the last operation that modified program data. This step can be repeated. The user can undo only those operations made during the current run of the program.
---
### 2. Expenses
Manage a list of expenses. Each expense has a `day` (integer between 1 and 30), `amount` of money (positive integer) and expense `type` (string). Provide the following features:
1. Add an expense. Expense data is read from the console.
2. Display the list of expenses.
3. Filter the list so that it contains only expenses above a certain value read from the console.
4. Undo the last operation that modified program data. This step can be repeated. The user can undo only those operations made during the current run of the program.
---
### 3. Students
Manage a list of students. Each student has an `id` (integer, unique), a `name` (string) and a `group` (positive integer). Provide the following features:
1. Add a student. Student data is read from the console.
2. Display the list of students.
3. Filter the list so that students in a given group (read from the console) are deleted from the list.
4. Undo the last operation that modified program data. This step can be repeated. The user can undo only those operations made during the current run of the program.
---
### 4. Books
Manage a list of books. Each book has an `isbn` (string, unique), an `author` and a `title` (strings). Provide the following features:
1. Add a book. Book data is read from the console.
2. Display the list of books.
3. Filter the list so that book titles starting with a given word are deleted from the list.
4. Undo the last operation that modified program data. This step can be repeated. The user can undo only those operations made during the current run of the program.
## Bonus possibility (0.1p, deadline week 11)
- In addition to the file-based implementations above, implement a repository that uses either a JSON or an XML file for storage (at your choice).
## Bonus possibility (0.1p, deadline week 11)
- Use a `settings.properties` file to decide which of the repository implementations to use. At startup, the program reads this input file and instantiates the correct repository. This allows changing the program's input file format without changing its source code.
## Bonus possibility (0.1p, deadline week 11)
- Implement a database-backed (SQL or NoSQL) repository. Use the database systems update functionalities properly (dont rewrite the entire database at each operation).
@@ -0,0 +1,19 @@
class Expense():
def __init__(self,day,money,type_of_expense):
self.day=day
self.money=money
self.type_of_expense=type_of_expense
def get_day(self):
return self.day
def get_money(self):
return self.money
def get_type_of_expense(self):
return self.type_of_expense
def set_day(self,day):
self.day=day
def set_money(self,money):
self.money=money
def set_type_of_expense(self,type_of_expense):
self.type_of_expense=type_of_expense
def __str__(self) -> str:
return "Day: {} | Money: {} | Type: {} ".format(self.day,self.money,self.type_of_expense)
@@ -0,0 +1,25 @@
from ui.ui import ui
from repositories.textfile import TextFileRepository
from repositories.memory import MemoryRepository
from repositories.binaryfile import BinaryFileRepository
from repositories.jsonfile import JsonFileRepository
from repositories.database import DatabaseRepository
if __name__=="__main__":
repos={
"textfile":TextFileRepository,
"memory":MemoryRepository,
"binaryfile":BinaryFileRepository,
"jsonfile":JsonFileRepository,
"database":DatabaseRepository
}
repo=None
with open("src/settings.properties") as f:
for line in f:
if line.strip().startswith("repository"):
repo=repos.get(line.split("\"",3)[1].strip(),MemoryRepository)()
break
ui=ui(repo)
ui()
@@ -0,0 +1,57 @@
[
{
"day": 22,
"money": 7,
"type_of_expense": "Gas"
},
{
"day": 25,
"money": 90,
"type_of_expense": "Water"
},
{
"day": 11,
"money": 20,
"type_of_expense": "Netflix Subscription"
},
{
"day": 17,
"money": 14,
"type_of_expense": "Water"
},
{
"day": 9,
"money": 39,
"type_of_expense": "Water"
},
{
"day": 27,
"money": 4,
"type_of_expense": "Water"
},
{
"day": 27,
"money": 84,
"type_of_expense": "Electricity"
},
{
"day": 16,
"money": 63,
"type_of_expense": "Water"
},
{
"day": 20,
"money": 69,
"type_of_expense": "Electricity"
},
{
"day": 3,
"money": 19,
"type_of_expense": "Gas"
},
{
"day": 12,
"money": 432,
"type_of_expense": "gnwrjg"
}
]
@@ -0,0 +1,7 @@
25 90 Water
11 20 Netflix Subscription
9 39 Water
27 84 Electricity
16 63 Water
20 69 Electricity
3 19 Gas
@@ -0,0 +1,48 @@
from repositories.repository import Repository
import pickle
class BinaryFileRepository(Repository):
def __init__(self,path="src/memoryfiles/binarymemory.bin"):
self.file_path=path
self.previous_list_of_expenses=[]
with open(self.file_path,"ab") as f:
pass
def insert_element(self, expense):
"""
:params:
expense - Expense
The function appends the current state of the repo to the stack
The function takes the expense and dumps it into the file indicated by the file_path as a binary representation
"""
self.previous_list_of_expenses.append(self.get_elements())
with open(self.file_path,"ab") as f:
pickle.dump(expense,f)
def get_elements(self):
list_of_expenses=[]
with open(self.file_path,"rb") as f:
while True:
try:
list_of_expenses.append(pickle.load(f))
except EOFError:
break
return list_of_expenses
def filter_elements(self, value):
self.previous_list_of_expenses.append(self.get_elements())
list_of_expenses=[]
with open(self.file_path,"rb") as f:
while True:
try:
expense=pickle.load(f)
if expense.get_money()<value:
continue
list_of_expenses.append(expense)
except EOFError:
break
with open(self.file_path,"wb") as f:
for expense in list_of_expenses:
pickle.dump(expense,f)
def undo(self):
with open(self.file_path,"wb") as f:
for expense in self.previous_list_of_expenses.pop():
pickle.dump(expense,f)
@@ -0,0 +1,26 @@
from pymongo import MongoClient
from domain.domain import Expense
class DatabaseRepository():
def __init__(self):
self.connection_string="mongodb://DanielCujba:kT99aAmXD2DXXru@ac-72p9ntw-shard-00-00.c6jcmwa.mongodb.net:27017,ac-72p9ntw-shard-00-01.c6jcmwa.mongodb.net:27017,ac-72p9ntw-shard-00-02.c6jcmwa.mongodb.net:27017/?ssl=true&replicaSet=atlas-a0t2fd-shard-0&authSource=admin&retryWrites=true&w=majority"
self.client = MongoClient(self.connection_string)
self.db = self.client['FP']
self.collection=self.db['Expenses']
self.undos=[]
def insert_element(self,expense):
id_inserted=self.collection.insert_one(self.toJson(expense)).inserted_id
self.undos.append(id_inserted)
def get_elements(self):
return [Expense(int(element["day"]),int(element["money"]),element["type_of_expense"]) for element in self.collection.find()]
def filter_elements(self,value):
self.undos.append([el for el in self.collection.find({"money":{"$lt":value}})])
self.collection.delete_many({"money":{"$lt":value}})
def undo(self):
e=self.undos.pop()
if type(e)==list:
self.collection.insert_many(e)
else:
self.collection.delete_one({"_id":e})
def toJson(self,expense):
return {"day":expense.get_day(),"money":expense.get_money(),"type_of_expense":expense.get_type_of_expense()}
@@ -0,0 +1,35 @@
from repositories.repository import Repository
from domain.domain import Expense
import json
class JsonFileRepository(Repository):
def __init__(self):
self.file_path="src/memoryfiles/jsonmemory.json"
self.previous_list_of_expenses=[]
with open(self.file_path,"a") as f:
pass
def insert_element(self, expense):
expenses=None
with open(self.file_path,"r") as f:
expenses=json.load(f)
self.previous_list_of_expenses.append(expenses)
expenses.append(self.toJson(expense))
with open(self.file_path,"w") as f:
json.dump(expenses,f,indent=4)
def get_elements(self):
with open(self.file_path,"r") as f:
json_obj=json.load(f)
return [Expense(int(expense["day"]),int(expense["money"]),expense["type_of_expense"]) for expense in json_obj]
def filter_elements(self, value):
expenses=None
with open(self.file_path,"r") as f:
expenses=json.load(f)
self.previous_list_of_expenses.append(expenses)
expenses=[expense for expense in expenses if expense["money"]>=value]
with open(self.file_path,"w") as f:
json.dump(expenses,f,indent=4)
def undo(self):
with open(self.file_path,"w") as f:
json.dump(self.previous_list_of_expenses.pop(),f,indent=4)
def toJson(self,expense):
return {"day":expense.get_day(),"money":expense.get_money(),"type_of_expense":expense.get_type_of_expense()}
@@ -0,0 +1,28 @@
from repositories.repository import Repository
from domain.domain import Expense
import random
class MemoryRepository(Repository):
def __init__(self):
super().__init__()
self.list_of_expenses=self.generate_elements(10)
self.previous_list_of_expenses=[]
def insert_element(self,expense):
"""
:params:
expense - Expense
The function takes the current state of the repo and appends it to the stack
The function appends the expense to the current list of expenses
"""
self.previous_list_of_expenses.append(self.list_of_expenses)
self.list_of_expenses.append(expense)
def get_elements(self):
return self.list_of_expenses
def filter_elements(self,value):
self.previous_list_of_expenses.append(self.list_of_expenses)
self.list_of_expenses=list(filter(lambda x:x.get_money()>value,self.list_of_expenses))
def generate_elements(self,number)->list:
types={1:"Water",2:"Gas",3:"Electricity"}
return [Expense(random.randint(1,30),random.randint(1,100),types[random.randint(1,3)]) for _ in range(number)]
def undo(self):
self.list_of_expenses=self.previous_list_of_expenses.pop()
@@ -0,0 +1,13 @@
from domain.domain import Expense
class Repository():
def __init__(self):
pass
def get_elements(self):
pass
def insert_element(self,expense:Expense):
pass
def filter_elements(self,value:int):
pass
def undo():
pass
@@ -0,0 +1,45 @@
from repositories.repository import Repository
from domain.domain import Expense
class TextFileRepository(Repository):
def __init__(self,path="src/memoryfiles/textmemory.txt"):
self.file_path=path
self.previous_list_of_expenses=[]
with open(self.file_path,"a") as f:
pass
def insert_element(self, expense):
"""
:params:
expense - Expense
The function appends the current state of the repo on the stack
The function takes an expense and stores it in the format "{day} {money} {type_of_expense}" in the text file indicated by the file_path
"""
self.previous_list_of_expenses.append(self.get_elements())
with open(self.file_path,"a") as f:
f.write("{} {} {}\n".format(expense.get_day(),expense.get_money(),expense.get_type_of_expense()))
def get_elements(self):
list_of_expenses=[]
with open(self.file_path,"r") as f:
for line in f:
data=line.strip().split(' ',2)
list_of_expenses.append(Expense(int(data[0]),int(data[1]),data[2]))
return list_of_expenses
def filter_elements(self, value):
self.previous_list_of_expenses.append(self.get_elements())
list_of_expenses=[]
with open(self.file_path,'r') as f:
for line in f:
data=line.strip().split(' ',2)
if int(data[1])<value:
continue
list_of_expenses.append(Expense(int(data[0]),int(data[1]),data[2]))
with open(self.file_path,'w') as f:
for expense in list_of_expenses:
f.write("{} {} {}\n".format(expense.get_day(),expense.get_money(),expense.get_type_of_expense()))
def undo(self):
with open(self.file_path,'w') as f:
for expense in self.previous_list_of_expenses.pop():
f.write("{} {} {}\n".format(expense.get_day(),expense.get_money(),expense.get_type_of_expense()))
@@ -0,0 +1,31 @@
from domain.domain import Expense
from repositories.repository import Repository
class Services():
def __init__(self,repo:Repository) -> None:
self.repo=repo
self.undos=0
def add_expense(self,day,money,type_of_expense):
"""
:params:
day - integer between 1 and 30
money - positive integer
type_of_expense - string
The function creates a new Expense object and call the repository's insert_element method
The function increments the undo counter
"""
expense=Expense(day,money,type_of_expense)
self.repo.insert_element(expense)
self.undos+=1
def display_list(self):
return self.repo.get_elements()
def filter_list(self,value):
self.undos+=1
self.repo.filter_elements(value)
def undo(self):
if self.undos>0:
self.undos-=1
self.repo.undo()
else:
raise Exception("No more undos available")
@@ -0,0 +1,3 @@
{
repository: "database",
}
@@ -0,0 +1,52 @@
[
{
"day": 22,
"money": 7,
"type_of_expense": "Gas"
},
{
"day": 25,
"money": 90,
"type_of_expense": "Water"
},
{
"day": 11,
"money": 20,
"type_of_expense": "Netflix Subscription"
},
{
"day": 17,
"money": 14,
"type_of_expense": "Water"
},
{
"day": 9,
"money": 39,
"type_of_expense": "Water"
},
{
"day": 27,
"money": 4,
"type_of_expense": "Water"
},
{
"day": 27,
"money": 84,
"type_of_expense": "Electricity"
},
{
"day": 16,
"money": 63,
"type_of_expense": "Water"
},
{
"day": 20,
"money": 69,
"type_of_expense": "Electricity"
},
{
"day": 3,
"money": 19,
"type_of_expense": "Gas"
}
]
@@ -0,0 +1,10 @@
22 7 Gas
25 90 Water
11 20 Netflix Subscription
17 14 Water
9 39 Water
27 4 Water
27 84 Electricity
16 63 Water
20 69 Electricity
3 19 Gas
@@ -0,0 +1,47 @@
from repositories.textfile import TextFileRepository
from repositories.memory import MemoryRepository
from repositories.binaryfile import BinaryFileRepository
from domain.domain import Expense
import unittest
class UnitTester(unittest.TestCase):
def test_memory_repo(self):
repo=MemoryRepository()
list_of_expenses=repo.get_elements()
e=Expense(10,200,"Gucci")
list_of_expenses.append(e)
repo.insert_element(e)
self.assertEqual(list_of_expenses,repo.get_elements())
def test_textfile_repo(self):
repo=TextFileRepository(path="src/templetes/textmemory.bin")
e=Expense(10,200,"Gucci")
list_of_expenses=repo.get_elements()
repo.insert_element(e)
list_of_expenses.append(e)
repo_list=repo.get_elements()
self.assertEqual(len(list_of_expenses),len(repo_list))
for i in range(len(list_of_expenses)):
self.assertEqual(list_of_expenses[i].get_day(),repo_list[i].get_day())
self.assertEqual(list_of_expenses[i].get_money(),repo_list[i].get_money())
self.assertEqual(list_of_expenses[i].get_type_of_expense(),repo_list[i].get_type_of_expense())
repo.undo()
def test_binaryfile_repo(self):
repo=BinaryFileRepository(path="src/templetes/binarymemory.bin")
e=Expense(10,200,"Gucci")
list_of_expenses=repo.get_elements()
repo.insert_element(e)
list_of_expenses.append(e)
repo_list=repo.get_elements()
self.assertEqual(len(list_of_expenses),len(repo_list))
for i in range(len(list_of_expenses)):
self.assertEqual(list_of_expenses[i].get_day(),repo_list[i].get_day())
self.assertEqual(list_of_expenses[i].get_money(),repo_list[i].get_money())
self.assertEqual(list_of_expenses[i].get_type_of_expense(),repo_list[i].get_type_of_expense())
repo.undo()
if __name__=="__main__":
unittest.main()
@@ -0,0 +1,49 @@
from services.services import Services
import re
class ui():
def __init__(self,repo) -> None:
self.repo=repo
self.services=Services(self.repo)
def __call__(self) -> None:
while True:
print("""
Please select a command:
1. Add an expense
2. Display the list of expenses
3. Filter the list
4. Undo
0. Exit
""")
inp=input(">")
match inp:
case "1":
day=input("Please enter the day: ")
if not re.fullmatch("\d+",day) or int(day)<1 or int(day)>30:
print("Invalid day. Value must be an integer between 1 and 30")
continue
money=input("Please enter the amount of money: ")
if not re.fullmatch("\d+",money):
print("Invalid amount of money. Value must be a positive integer")
continue
type_of_expense=input("Please enter the type of expense: ")
self.services.add_expense(int(day),int(money),type_of_expense)
case "2":
for expense in self.services.display_list():
print(expense)
case "3":
value=input("Please enter a value: ")
if not re.fullmatch("\d+",value):
print("Invalid number. Value must be a positive integer")
continue
self.services.filter_list(int(value))
print("List filtered")
case "4":
try:
self.services.undo()
except Exception as e:
print(e)
case "0":
return
case _:
print("Invalid Command")