diff --git a/.github/workflows/ruff-ci.yml b/.github/workflows/ruff-ci.yml
new file mode 100644
index 00000000..d2aa947b
--- /dev/null
+++ b/.github/workflows/ruff-ci.yml
@@ -0,0 +1,24 @@
+name: Lint with Ruff
+
+on:
+ push:
+ branches: [ "main", "master", "second-branch" ] # запуск при пуше в main/master
+ pull_request:
+ branches: [ "main", "master", "second-branch" ] # запуск при создании PR в main/master
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4 # клонирует репозиторий
+
+ - name: Set up Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: "3.11"
+
+ - name: Install Ruff
+ run: pip install ruff
+
+ - name: Run Ruff linting
+ run: ruff check . # проверяет весь репозиторий
diff --git a/.gitignore b/.gitignore
index 82f92755..67ff1a6b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -154,6 +154,7 @@ dmypy.json
# Cython debug symbols
cython_debug/
+my_db.json
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 00000000..1c2fda56
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/fastapi-backend-course.iml b/.idea/fastapi-backend-course.iml
new file mode 100644
index 00000000..2946dc0d
--- /dev/null
+++ b/.idea/fastapi-backend-course.iml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 00000000..880ed7eb
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 00000000..105ce2da
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml
new file mode 100644
index 00000000..bb12ef93
--- /dev/null
+++ b/.idea/material_theme_project_new.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 00000000..b7f038d2
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 00000000..5b24bd97
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 00000000..c8397c94
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/git/src/main.py b/git/src/main.py
index 1822c7e9..61b6419e 100644
--- a/git/src/main.py
+++ b/git/src/main.py
@@ -14,7 +14,7 @@ def load_books(filename='library.json'):
except json.JSONDecodeError:
return []
-def save_books(books, filename='library.json'):
+def saving_books(books, filename='library.json'):
"""
Сохранение списка книг в JSON-файл.
"""
@@ -94,7 +94,7 @@ def main():
# Получаем новый список с добавленной книгой
new_books = add_book(books, title, author, year)
books = new_books # Обновляем переменную, чтобы сохранить изменения
- save_books(books) # Сразу сохраняем в файл
+ saving_books(books) # Сразу сохраняем в файл
print("Книга добавлена!")
elif choice == '3':
@@ -102,9 +102,9 @@ def main():
title_to_remove = input("Введите название книги, которую хотите удалить: ").strip()
new_books = remove_book(books, title_to_remove)
- if len(new_books) < len(books):
+ if len(new_books) > len(books):
books = new_books
- save_books(books)
+ saving_books(books)
print("Книга удалена!")
else:
print("Книга с таким названием не найдена.")
@@ -126,5 +126,10 @@ def main():
else:
print("Некорректный ввод. Попробуйте ещё раз.")
+
+
+
+
+
if __name__ == "__main__":
- main()
+ main()
\ No newline at end of file
diff --git a/simple_backend/src/task_tracker/db.py b/simple_backend/src/task_tracker/db.py
new file mode 100644
index 00000000..084b7b74
--- /dev/null
+++ b/simple_backend/src/task_tracker/db.py
@@ -0,0 +1,211 @@
+from typing import List, Dict, Any, Optional, Union
+
+from abc import ABCMeta, abstractmethod, abstractproperty
+
+import os
+import json
+import functools
+
+import requests
+
+
+def id_to_str(func):
+ @functools.wraps(func)
+ def wrapper(*args, **kwargs):
+ if 'id' in kwargs:
+ kwargs['id'] = str(kwargs['id'])
+ return func(*args, **kwargs)
+ elif len(kwargs) > 1:
+ new_args = (args[0], str(args[1])) + args[2:]
+ return func(*new_args, **kwargs)
+ else:
+ return func(*args, **kwargs)
+ return wrapper
+
+class absDB():
+ @abstractmethod
+ def get_all(self) -> List[Dict[str, Any]]:
+ pass
+
+ @abstractmethod
+ def get(self, id) -> Dict[str, Any] | None:
+ pass
+
+ @abstractmethod
+ def add(self, id, data) -> bool:
+ pass
+
+ @abstractmethod
+ def delete(self, id) -> bool:
+ pass
+
+ @abstractmethod
+ def update(self, id, data) -> bool:
+ pass
+
+class ListDB(absDB):
+ def init(self) -> None:
+ self.tasks: Dict[int, Dict[str, Any]] = {}
+
+ def get_all(self) -> List[Dict[str, Any]]:
+ return list(self.tasks.values())
+
+ def get(self, id) -> Dict[str, Any] | None:
+ return self.tasks[id]
+
+ def add(self, id, data) -> bool:
+ if str(id) in data:
+ return False
+ self.tasks[id] = data
+ return True
+
+ def delete(self, id) -> bool:
+ if id in self.tasks:
+ del self.tasks[id]
+ return True
+ return False
+
+ def update(self, id, data):
+ if id in self.tasks:
+ self.tasks[id].update(data)
+ return True
+ return False
+
+class FileDB(absDB):
+ def __init__(self) -> None:
+ self.__name_file = 'my_db.json'
+ print('File', self.__name_file)
+
+
+ def get_all(self) -> List[Dict[str, Any]]:
+ data = self.__read_json()
+ return list(data.values())
+
+ @id_to_str
+ def get(self, id) -> Dict[str, Any] | None:
+ data_from_file = self.__read_json()
+ if id in data_from_file:
+ return data_from_file[id]
+ return None
+
+ @id_to_str
+ def add(self, id, data) -> bool:
+ data_from_file = self.__read_json()
+ if id in data_from_file:
+ return False
+ data_from_file[id] = data
+ self.__save_json(data_from_file)
+ return True
+
+ @id_to_str
+ def delete(self, id) -> bool:
+ data_from_file = self.__read_json()
+ if id in data_from_file:
+ del data_from_file[id]
+ self.__save_json(data_from_file)
+ return True
+ return False
+
+ @id_to_str
+ def update(self, id, data):
+ data_from_file = self.__read_json()
+ if id in data_from_file:
+ data_from_file[id] = data
+ self.__save_json(data_from_file)
+ return True
+ return False
+
+ def __save_json(self, data):
+ with open(self.__name_file, 'w' '') as file:
+ json.dump(data, file, ensure_ascii=False, indent=4)
+ return True
+
+ def __read_json(self):
+ print('File', self.__name_file)
+ try:
+ with open(self.__name_file, 'r' '') as file:
+ print('File', file)
+ try:
+ data = json.load(file)
+ except json.JSONDecodeError :
+ print(12313)
+ self.__save_json({})
+ data = {}
+ except FileNotFoundError:
+ self.__save_json({})
+ data = {}
+
+ return data
+
+class CloudDB(absDB):
+ def __init__(self) -> None:
+ self.key_api = '$2a$10$gkEEHNtdmq7FCAUiGF88k.IDmciahKhZmVL7ZBfbANA6US9nsQsu6'
+ key_bin = '68b1c53ed0ea881f406a555a'
+ root = 'https://api.jsonbin.io/v3'
+ self.url = f"{root}/b/{key_bin}"
+
+ def get_all(self) -> List[Dict[str, Any]]:
+ return list(self.__get_json().values())
+
+ @id_to_str
+ def get(self, id) -> Dict[str, Any] | None:
+ old_data = self.__get_json()
+ if id in old_data:
+ return old_data[id]
+ return None
+
+ @id_to_str
+ def add(self, id, data) -> bool:
+ old_data = self.__get_json()
+ if id in old_data:
+ return False
+ old_data[id] = data
+ self.__post_json(old_data)
+ return True
+
+ @id_to_str
+ def delete(self, id) -> bool:
+ data = self.__get_json()
+ if id in data:
+ del data[id]
+ self.__post_json(data)
+ return True
+ return False
+
+ @id_to_str
+ def update(self, id, data) -> bool:
+ all_data = self.__get_json()
+ if str(id) in all_data:
+ all_data[str(id)] = data
+ self.__post_json(all_data)
+ return True
+ return False
+
+
+ def __get_json(self):
+ headers = {
+ 'X-Master-Key': self.key_api,
+ 'X-Bin-Meta': 'false',
+ }
+
+ req = requests.get(self.url, headers=headers)
+
+ if req.status_code != 200:
+ raise Exception("API Error")
+ return req.json()
+
+ def __post_json(self, data):
+ new_data = json.dumps(data)
+
+ headers = {
+ 'X-Master-Key': self.key_api,
+ 'Content-Type': 'application/json',
+ }
+
+ req = requests.put(self.url ,data=new_data, headers=headers)
+
+ if req.status_code != 200:
+ raise Exception("API Error")
+ return True
+
+list_db = FileDB()
diff --git a/simple_backend/src/task_tracker/main.py b/simple_backend/src/task_tracker/main.py
index 3db98d0d..331b9c5b 100644
--- a/simple_backend/src/task_tracker/main.py
+++ b/simple_backend/src/task_tracker/main.py
@@ -1,19 +1,55 @@
-from fastapi import FastAPI
+from fastapi import FastAPI, HTTPException, status
+from pydantic import BaseModel, EmailStr, Field, field_validator, ValidationError
+from db import list_db
+from typing import List
+
+from utils import TaskController, Worker_AI
app = FastAPI()
+task_controller = TaskController(list_db)
+
+class Task(BaseModel):
+ id: int
+ title: str = Field(default=..., min_length=1, max_length=50, description="Название задачи, от 1 до 50 символов")
+ text: str = Field(default=..., min_length=1, max_length=500, description="Текст задачи, от 1 до 500 символов")
+ status: str = Field(default=..., min_length=1, max_length=50, description="Статус задачи, от 1 до 50 символов")
@app.get("/tasks")
-def get_tasks():
- pass
+def get_tasks() -> List[Task]:
+ list_task = task_controller.get_all_task()
+ print('TASK', list_task)
+ tasks = [Task(**task) for task in list_task]
+ return tasks
+
@app.post("/tasks")
-def create_task(task):
- pass
+def create_task(task: Task):
+ ai = Worker_AI()
+ task.text += f'\nРешение: {ai.creating_solution_to_task(task.text)}\n'
+ task_controller.add_task(task.model_dump())
+ return task
@app.put("/tasks/{task_id}")
-def update_task(task_id: int):
- pass
-
+def update_task(task_id: int, task: Task):
+ existing_task = task_controller.update_task(task_id, task)
+ if existing_task:
+ return {"message": f"Task with id {task_id} update successfully"}
+ else:
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail=f"Task with id {task_id} not found"
+ )
+
@app.delete("/tasks/{task_id}")
def delete_task(task_id: int):
- pass
+ deleted = task_controller.delete_task(task_id)
+
+ if not deleted:
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail=f"Task with id {task_id} not found"
+ )
+ return {
+ "message": f"Task with id {task_id} deleted successfully",
+ "deleted_id": task_id
+ }
diff --git a/simple_backend/src/task_tracker/readme.md b/simple_backend/src/task_tracker/readme.md
new file mode 100644
index 00000000..5d857f75
--- /dev/null
+++ b/simple_backend/src/task_tracker/readme.md
@@ -0,0 +1,30 @@
+## Прочитайте, что такое "Хранение состояния", создайте в task_tracker readme.md файл и напишите в чём минусы подхода с хранением задач в оперативной памяти (списке python) ##
+
+1) При перезапуске сервера все состояния (задачи) будут потеряны
+2) Если мы имеем несколько инстансов, задачи между собой не будут синхронизированы
+
+## Что улучшилось после того, как список из оперативной памяти изменился на файл проекта? ##
+
+1) При перезапуске сервера данные не будут потеряны
+2) Данные более-менее синхронизированы и ими может польльзолваться несколько сервисов
+
+## Избавились ли мы таким способом от хранения состояния или нет? ##
+
+## Где еще можно хранить задачи и какие есть преимущества и недостатки этих подходов? ##
+
+1) В реляционной БД
+
+* Легкая маштабируемость
+* При перезапуске сервера данные не пропадут
+* Данные хранятся на жеском диске, следовательно долгие запросы
+
+2) В нереляционная БД
+
+* Легкая маштабируемость
+* Данные хранятся на оперативной памяти, быстрые запросы
+* При перезапуске сервера данные пропадут
+
+## Прочитайте что такое "состояние гонки" и напишите в readme файле о том, какие проблемы остались в бекенде на данном этапе проекта. Есть ли у вас какое-то решение этой проблемы? ##
+
+* Можно перенести хранение данных в БД
+* Можно использовать блокировку файла, чтобы запрос не мог начаться и перезаписать данные уже происходящего
diff --git a/simple_backend/src/task_tracker/task_tracker/bin/Activate.ps1 b/simple_backend/src/task_tracker/task_tracker/bin/Activate.ps1
new file mode 100644
index 00000000..b49d77ba
--- /dev/null
+++ b/simple_backend/src/task_tracker/task_tracker/bin/Activate.ps1
@@ -0,0 +1,247 @@
+<#
+.Synopsis
+Activate a Python virtual environment for the current PowerShell session.
+
+.Description
+Pushes the python executable for a virtual environment to the front of the
+$Env:PATH environment variable and sets the prompt to signify that you are
+in a Python virtual environment. Makes use of the command line switches as
+well as the `pyvenv.cfg` file values present in the virtual environment.
+
+.Parameter VenvDir
+Path to the directory that contains the virtual environment to activate. The
+default value for this is the parent of the directory that the Activate.ps1
+script is located within.
+
+.Parameter Prompt
+The prompt prefix to display when this virtual environment is activated. By
+default, this prompt is the name of the virtual environment folder (VenvDir)
+surrounded by parentheses and followed by a single space (ie. '(.venv) ').
+
+.Example
+Activate.ps1
+Activates the Python virtual environment that contains the Activate.ps1 script.
+
+.Example
+Activate.ps1 -Verbose
+Activates the Python virtual environment that contains the Activate.ps1 script,
+and shows extra information about the activation as it executes.
+
+.Example
+Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
+Activates the Python virtual environment located in the specified location.
+
+.Example
+Activate.ps1 -Prompt "MyPython"
+Activates the Python virtual environment that contains the Activate.ps1 script,
+and prefixes the current prompt with the specified string (surrounded in
+parentheses) while the virtual environment is active.
+
+.Notes
+On Windows, it may be required to enable this Activate.ps1 script by setting the
+execution policy for the user. You can do this by issuing the following PowerShell
+command:
+
+PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
+
+For more information on Execution Policies:
+https://go.microsoft.com/fwlink/?LinkID=135170
+
+#>
+Param(
+ [Parameter(Mandatory = $false)]
+ [String]
+ $VenvDir,
+ [Parameter(Mandatory = $false)]
+ [String]
+ $Prompt
+)
+
+<# Function declarations --------------------------------------------------- #>
+
+<#
+.Synopsis
+Remove all shell session elements added by the Activate script, including the
+addition of the virtual environment's Python executable from the beginning of
+the PATH variable.
+
+.Parameter NonDestructive
+If present, do not remove this function from the global namespace for the
+session.
+
+#>
+function global:deactivate ([switch]$NonDestructive) {
+ # Revert to original values
+
+ # The prior prompt:
+ if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
+ Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
+ Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
+ }
+
+ # The prior PYTHONHOME:
+ if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
+ Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
+ Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
+ }
+
+ # The prior PATH:
+ if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
+ Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
+ Remove-Item -Path Env:_OLD_VIRTUAL_PATH
+ }
+
+ # Just remove the VIRTUAL_ENV altogether:
+ if (Test-Path -Path Env:VIRTUAL_ENV) {
+ Remove-Item -Path env:VIRTUAL_ENV
+ }
+
+ # Just remove VIRTUAL_ENV_PROMPT altogether.
+ if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) {
+ Remove-Item -Path env:VIRTUAL_ENV_PROMPT
+ }
+
+ # Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
+ if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
+ Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
+ }
+
+ # Leave deactivate function in the global namespace if requested:
+ if (-not $NonDestructive) {
+ Remove-Item -Path function:deactivate
+ }
+}
+
+<#
+.Description
+Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
+given folder, and returns them in a map.
+
+For each line in the pyvenv.cfg file, if that line can be parsed into exactly
+two strings separated by `=` (with any amount of whitespace surrounding the =)
+then it is considered a `key = value` line. The left hand string is the key,
+the right hand is the value.
+
+If the value starts with a `'` or a `"` then the first and last character is
+stripped from the value before being captured.
+
+.Parameter ConfigDir
+Path to the directory that contains the `pyvenv.cfg` file.
+#>
+function Get-PyVenvConfig(
+ [String]
+ $ConfigDir
+) {
+ Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"
+
+ # Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
+ $pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue
+
+ # An empty map will be returned if no config file is found.
+ $pyvenvConfig = @{ }
+
+ if ($pyvenvConfigPath) {
+
+ Write-Verbose "File exists, parse `key = value` lines"
+ $pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath
+
+ $pyvenvConfigContent | ForEach-Object {
+ $keyval = $PSItem -split "\s*=\s*", 2
+ if ($keyval[0] -and $keyval[1]) {
+ $val = $keyval[1]
+
+ # Remove extraneous quotations around a string value.
+ if ("'""".Contains($val.Substring(0, 1))) {
+ $val = $val.Substring(1, $val.Length - 2)
+ }
+
+ $pyvenvConfig[$keyval[0]] = $val
+ Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
+ }
+ }
+ }
+ return $pyvenvConfig
+}
+
+
+<# Begin Activate script --------------------------------------------------- #>
+
+# Determine the containing directory of this script
+$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
+$VenvExecDir = Get-Item -Path $VenvExecPath
+
+Write-Verbose "Activation script is located in path: '$VenvExecPath'"
+Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
+Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
+
+# Set values required in priority: CmdLine, ConfigFile, Default
+# First, get the location of the virtual environment, it might not be
+# VenvExecDir if specified on the command line.
+if ($VenvDir) {
+ Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
+}
+else {
+ Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
+ $VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
+ Write-Verbose "VenvDir=$VenvDir"
+}
+
+# Next, read the `pyvenv.cfg` file to determine any required value such
+# as `prompt`.
+$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
+
+# Next, set the prompt from the command line, or the config file, or
+# just use the name of the virtual environment folder.
+if ($Prompt) {
+ Write-Verbose "Prompt specified as argument, using '$Prompt'"
+}
+else {
+ Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
+ if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
+ Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
+ $Prompt = $pyvenvCfg['prompt'];
+ }
+ else {
+ Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)"
+ Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
+ $Prompt = Split-Path -Path $venvDir -Leaf
+ }
+}
+
+Write-Verbose "Prompt = '$Prompt'"
+Write-Verbose "VenvDir='$VenvDir'"
+
+# Deactivate any currently active virtual environment, but leave the
+# deactivate function in place.
+deactivate -nondestructive
+
+# Now set the environment variable VIRTUAL_ENV, used by many tools to determine
+# that there is an activated venv.
+$env:VIRTUAL_ENV = $VenvDir
+
+if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {
+
+ Write-Verbose "Setting prompt to '$Prompt'"
+
+ # Set the prompt to include the env name
+ # Make sure _OLD_VIRTUAL_PROMPT is global
+ function global:_OLD_VIRTUAL_PROMPT { "" }
+ Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
+ New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt
+
+ function global:prompt {
+ Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
+ _OLD_VIRTUAL_PROMPT
+ }
+ $env:VIRTUAL_ENV_PROMPT = $Prompt
+}
+
+# Clear PYTHONHOME
+if (Test-Path -Path Env:PYTHONHOME) {
+ Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
+ Remove-Item -Path Env:PYTHONHOME
+}
+
+# Add the venv to the PATH
+Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
+$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"
diff --git a/simple_backend/src/task_tracker/task_tracker/bin/activate b/simple_backend/src/task_tracker/task_tracker/bin/activate
new file mode 100644
index 00000000..049d90dd
--- /dev/null
+++ b/simple_backend/src/task_tracker/task_tracker/bin/activate
@@ -0,0 +1,69 @@
+# This file must be used with "source bin/activate" *from bash*
+# you cannot run it directly
+
+deactivate () {
+ # reset old environment variables
+ if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
+ PATH="${_OLD_VIRTUAL_PATH:-}"
+ export PATH
+ unset _OLD_VIRTUAL_PATH
+ fi
+ if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
+ PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
+ export PYTHONHOME
+ unset _OLD_VIRTUAL_PYTHONHOME
+ fi
+
+ # This should detect bash and zsh, which have a hash command that must
+ # be called to get it to forget past commands. Without forgetting
+ # past commands the $PATH changes we made may not be respected
+ if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
+ hash -r 2> /dev/null
+ fi
+
+ if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
+ PS1="${_OLD_VIRTUAL_PS1:-}"
+ export PS1
+ unset _OLD_VIRTUAL_PS1
+ fi
+
+ unset VIRTUAL_ENV
+ unset VIRTUAL_ENV_PROMPT
+ if [ ! "${1:-}" = "nondestructive" ] ; then
+ # Self destruct!
+ unset -f deactivate
+ fi
+}
+
+# unset irrelevant variables
+deactivate nondestructive
+
+VIRTUAL_ENV=/home/sterd/mentor/fastapi-backend-course/simple_backend/src/task_tracker/task_tracker
+export VIRTUAL_ENV
+
+_OLD_VIRTUAL_PATH="$PATH"
+PATH="$VIRTUAL_ENV/"bin":$PATH"
+export PATH
+
+# unset PYTHONHOME if set
+# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
+# could use `if (set -u; : $PYTHONHOME) ;` in bash
+if [ -n "${PYTHONHOME:-}" ] ; then
+ _OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
+ unset PYTHONHOME
+fi
+
+if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
+ _OLD_VIRTUAL_PS1="${PS1:-}"
+ PS1='(task_tracker) '"${PS1:-}"
+ export PS1
+ VIRTUAL_ENV_PROMPT='(task_tracker) '
+ export VIRTUAL_ENV_PROMPT
+fi
+
+# This should detect bash and zsh, which have a hash command that must
+# be called to get it to forget past commands. Without forgetting
+# past commands the $PATH changes we made may not be respected
+if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
+ hash -r 2> /dev/null
+fi
diff --git a/simple_backend/src/task_tracker/task_tracker/bin/activate.csh b/simple_backend/src/task_tracker/task_tracker/bin/activate.csh
new file mode 100644
index 00000000..405d2851
--- /dev/null
+++ b/simple_backend/src/task_tracker/task_tracker/bin/activate.csh
@@ -0,0 +1,26 @@
+# This file must be used with "source bin/activate.csh" *from csh*.
+# You cannot run it directly.
+# Created by Davide Di Blasi .
+# Ported to Python 3.3 venv by Andrew Svetlov
+
+alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate'
+
+# Unset irrelevant variables.
+deactivate nondestructive
+
+setenv VIRTUAL_ENV /home/sterd/mentor/fastapi-backend-course/simple_backend/src/task_tracker/task_tracker
+
+set _OLD_VIRTUAL_PATH="$PATH"
+setenv PATH "$VIRTUAL_ENV/"bin":$PATH"
+
+
+set _OLD_VIRTUAL_PROMPT="$prompt"
+
+if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
+ set prompt = '(task_tracker) '"$prompt"
+ setenv VIRTUAL_ENV_PROMPT '(task_tracker) '
+endif
+
+alias pydoc python -m pydoc
+
+rehash
diff --git a/simple_backend/src/task_tracker/task_tracker/bin/activate.fish b/simple_backend/src/task_tracker/task_tracker/bin/activate.fish
new file mode 100644
index 00000000..0cf16940
--- /dev/null
+++ b/simple_backend/src/task_tracker/task_tracker/bin/activate.fish
@@ -0,0 +1,69 @@
+# This file must be used with "source /bin/activate.fish" *from fish*
+# (https://fishshell.com/); you cannot run it directly.
+
+function deactivate -d "Exit virtual environment and return to normal shell environment"
+ # reset old environment variables
+ if test -n "$_OLD_VIRTUAL_PATH"
+ set -gx PATH $_OLD_VIRTUAL_PATH
+ set -e _OLD_VIRTUAL_PATH
+ end
+ if test -n "$_OLD_VIRTUAL_PYTHONHOME"
+ set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
+ set -e _OLD_VIRTUAL_PYTHONHOME
+ end
+
+ if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
+ set -e _OLD_FISH_PROMPT_OVERRIDE
+ # prevents error when using nested fish instances (Issue #93858)
+ if functions -q _old_fish_prompt
+ functions -e fish_prompt
+ functions -c _old_fish_prompt fish_prompt
+ functions -e _old_fish_prompt
+ end
+ end
+
+ set -e VIRTUAL_ENV
+ set -e VIRTUAL_ENV_PROMPT
+ if test "$argv[1]" != "nondestructive"
+ # Self-destruct!
+ functions -e deactivate
+ end
+end
+
+# Unset irrelevant variables.
+deactivate nondestructive
+
+set -gx VIRTUAL_ENV /home/sterd/mentor/fastapi-backend-course/simple_backend/src/task_tracker/task_tracker
+
+set -gx _OLD_VIRTUAL_PATH $PATH
+set -gx PATH "$VIRTUAL_ENV/"bin $PATH
+
+# Unset PYTHONHOME if set.
+if set -q PYTHONHOME
+ set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
+ set -e PYTHONHOME
+end
+
+if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
+ # fish uses a function instead of an env var to generate the prompt.
+
+ # Save the current fish_prompt function as the function _old_fish_prompt.
+ functions -c fish_prompt _old_fish_prompt
+
+ # With the original prompt function renamed, we can override with our own.
+ function fish_prompt
+ # Save the return status of the last command.
+ set -l old_status $status
+
+ # Output the venv prompt; color taken from the blue of the Python logo.
+ printf "%s%s%s" (set_color 4B8BBE) '(task_tracker) ' (set_color normal)
+
+ # Restore the return status of the previous command.
+ echo "exit $old_status" | .
+ # Output the original/"old" prompt.
+ _old_fish_prompt
+ end
+
+ set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
+ set -gx VIRTUAL_ENV_PROMPT '(task_tracker) '
+end
diff --git a/simple_backend/src/task_tracker/task_tracker/bin/dotenv b/simple_backend/src/task_tracker/task_tracker/bin/dotenv
new file mode 100755
index 00000000..4823cbad
--- /dev/null
+++ b/simple_backend/src/task_tracker/task_tracker/bin/dotenv
@@ -0,0 +1,8 @@
+#!/home/sterd/mentor/fastapi-backend-course/simple_backend/src/task_tracker/task_tracker/bin/python
+# -*- coding: utf-8 -*-
+import re
+import sys
+from dotenv.__main__ import cli
+if __name__ == '__main__':
+ sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+ sys.exit(cli())
diff --git a/simple_backend/src/task_tracker/task_tracker/bin/fastapi b/simple_backend/src/task_tracker/task_tracker/bin/fastapi
new file mode 100755
index 00000000..f4ad8cfb
--- /dev/null
+++ b/simple_backend/src/task_tracker/task_tracker/bin/fastapi
@@ -0,0 +1,8 @@
+#!/home/sterd/mentor/fastapi-backend-course/simple_backend/src/task_tracker/task_tracker/bin/python
+# -*- coding: utf-8 -*-
+import re
+import sys
+from fastapi.cli import main
+if __name__ == '__main__':
+ sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+ sys.exit(main())
diff --git a/simple_backend/src/task_tracker/task_tracker/bin/normalizer b/simple_backend/src/task_tracker/task_tracker/bin/normalizer
new file mode 100755
index 00000000..2e342db1
--- /dev/null
+++ b/simple_backend/src/task_tracker/task_tracker/bin/normalizer
@@ -0,0 +1,8 @@
+#!/home/sterd/mentor/fastapi-backend-course/simple_backend/src/task_tracker/task_tracker/bin/python
+# -*- coding: utf-8 -*-
+import re
+import sys
+from charset_normalizer.cli import cli_detect
+if __name__ == '__main__':
+ sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+ sys.exit(cli_detect())
diff --git a/simple_backend/src/task_tracker/task_tracker/bin/pip b/simple_backend/src/task_tracker/task_tracker/bin/pip
new file mode 100755
index 00000000..6b8b0d6f
--- /dev/null
+++ b/simple_backend/src/task_tracker/task_tracker/bin/pip
@@ -0,0 +1,8 @@
+#!/home/sterd/mentor/fastapi-backend-course/simple_backend/src/task_tracker/task_tracker/bin/python
+# -*- coding: utf-8 -*-
+import re
+import sys
+from pip._internal.cli.main import main
+if __name__ == '__main__':
+ sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+ sys.exit(main())
diff --git a/simple_backend/src/task_tracker/task_tracker/bin/pip3 b/simple_backend/src/task_tracker/task_tracker/bin/pip3
new file mode 100755
index 00000000..6b8b0d6f
--- /dev/null
+++ b/simple_backend/src/task_tracker/task_tracker/bin/pip3
@@ -0,0 +1,8 @@
+#!/home/sterd/mentor/fastapi-backend-course/simple_backend/src/task_tracker/task_tracker/bin/python
+# -*- coding: utf-8 -*-
+import re
+import sys
+from pip._internal.cli.main import main
+if __name__ == '__main__':
+ sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+ sys.exit(main())
diff --git a/simple_backend/src/task_tracker/task_tracker/bin/pip3.10 b/simple_backend/src/task_tracker/task_tracker/bin/pip3.10
new file mode 100755
index 00000000..6b8b0d6f
--- /dev/null
+++ b/simple_backend/src/task_tracker/task_tracker/bin/pip3.10
@@ -0,0 +1,8 @@
+#!/home/sterd/mentor/fastapi-backend-course/simple_backend/src/task_tracker/task_tracker/bin/python
+# -*- coding: utf-8 -*-
+import re
+import sys
+from pip._internal.cli.main import main
+if __name__ == '__main__':
+ sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+ sys.exit(main())
diff --git a/simple_backend/src/task_tracker/task_tracker/bin/py.test b/simple_backend/src/task_tracker/task_tracker/bin/py.test
new file mode 100755
index 00000000..cea7f6c7
--- /dev/null
+++ b/simple_backend/src/task_tracker/task_tracker/bin/py.test
@@ -0,0 +1,8 @@
+#!/home/sterd/mentor/fastapi-backend-course/simple_backend/src/task_tracker/task_tracker/bin/python
+# -*- coding: utf-8 -*-
+import re
+import sys
+from pytest import console_main
+if __name__ == '__main__':
+ sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+ sys.exit(console_main())
diff --git a/simple_backend/src/task_tracker/task_tracker/bin/pygmentize b/simple_backend/src/task_tracker/task_tracker/bin/pygmentize
new file mode 100755
index 00000000..51d6642f
--- /dev/null
+++ b/simple_backend/src/task_tracker/task_tracker/bin/pygmentize
@@ -0,0 +1,8 @@
+#!/home/sterd/mentor/fastapi-backend-course/simple_backend/src/task_tracker/task_tracker/bin/python
+# -*- coding: utf-8 -*-
+import re
+import sys
+from pygments.cmdline import main
+if __name__ == '__main__':
+ sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+ sys.exit(main())
diff --git a/simple_backend/src/task_tracker/task_tracker/bin/pytest b/simple_backend/src/task_tracker/task_tracker/bin/pytest
new file mode 100755
index 00000000..cea7f6c7
--- /dev/null
+++ b/simple_backend/src/task_tracker/task_tracker/bin/pytest
@@ -0,0 +1,8 @@
+#!/home/sterd/mentor/fastapi-backend-course/simple_backend/src/task_tracker/task_tracker/bin/python
+# -*- coding: utf-8 -*-
+import re
+import sys
+from pytest import console_main
+if __name__ == '__main__':
+ sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+ sys.exit(console_main())
diff --git a/simple_backend/src/task_tracker/task_tracker/bin/python b/simple_backend/src/task_tracker/task_tracker/bin/python
new file mode 120000
index 00000000..acd4152a
--- /dev/null
+++ b/simple_backend/src/task_tracker/task_tracker/bin/python
@@ -0,0 +1 @@
+/usr/bin/python
\ No newline at end of file
diff --git a/simple_backend/src/task_tracker/task_tracker/bin/python3 b/simple_backend/src/task_tracker/task_tracker/bin/python3
new file mode 120000
index 00000000..d8654aa0
--- /dev/null
+++ b/simple_backend/src/task_tracker/task_tracker/bin/python3
@@ -0,0 +1 @@
+python
\ No newline at end of file
diff --git a/simple_backend/src/task_tracker/task_tracker/bin/python3.10 b/simple_backend/src/task_tracker/task_tracker/bin/python3.10
new file mode 120000
index 00000000..d8654aa0
--- /dev/null
+++ b/simple_backend/src/task_tracker/task_tracker/bin/python3.10
@@ -0,0 +1 @@
+python
\ No newline at end of file
diff --git a/simple_backend/src/task_tracker/task_tracker/bin/uvicorn b/simple_backend/src/task_tracker/task_tracker/bin/uvicorn
new file mode 100755
index 00000000..71a1a9e3
--- /dev/null
+++ b/simple_backend/src/task_tracker/task_tracker/bin/uvicorn
@@ -0,0 +1,8 @@
+#!/home/sterd/mentor/fastapi-backend-course/simple_backend/src/task_tracker/task_tracker/bin/python
+# -*- coding: utf-8 -*-
+import re
+import sys
+from uvicorn.main import main
+if __name__ == '__main__':
+ sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+ sys.exit(main())
diff --git a/simple_backend/src/task_tracker/task_tracker/bin/watchfiles b/simple_backend/src/task_tracker/task_tracker/bin/watchfiles
new file mode 100755
index 00000000..def4e346
--- /dev/null
+++ b/simple_backend/src/task_tracker/task_tracker/bin/watchfiles
@@ -0,0 +1,8 @@
+#!/home/sterd/mentor/fastapi-backend-course/simple_backend/src/task_tracker/task_tracker/bin/python
+# -*- coding: utf-8 -*-
+import re
+import sys
+from watchfiles.cli import cli
+if __name__ == '__main__':
+ sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+ sys.exit(cli())
diff --git a/simple_backend/src/task_tracker/task_tracker/bin/websockets b/simple_backend/src/task_tracker/task_tracker/bin/websockets
new file mode 100755
index 00000000..5f8ba9f0
--- /dev/null
+++ b/simple_backend/src/task_tracker/task_tracker/bin/websockets
@@ -0,0 +1,8 @@
+#!/home/sterd/mentor/fastapi-backend-course/simple_backend/src/task_tracker/task_tracker/bin/python
+# -*- coding: utf-8 -*-
+import re
+import sys
+from websockets.cli import main
+if __name__ == '__main__':
+ sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+ sys.exit(main())
diff --git a/simple_backend/src/task_tracker/task_tracker/lib64 b/simple_backend/src/task_tracker/task_tracker/lib64
new file mode 120000
index 00000000..7951405f
--- /dev/null
+++ b/simple_backend/src/task_tracker/task_tracker/lib64
@@ -0,0 +1 @@
+lib
\ No newline at end of file
diff --git a/simple_backend/src/task_tracker/task_tracker/pyvenv.cfg b/simple_backend/src/task_tracker/task_tracker/pyvenv.cfg
new file mode 100644
index 00000000..0537ffc0
--- /dev/null
+++ b/simple_backend/src/task_tracker/task_tracker/pyvenv.cfg
@@ -0,0 +1,3 @@
+home = /usr/bin
+include-system-site-packages = false
+version = 3.10.12
diff --git a/simple_backend/src/task_tracker/test_db.py b/simple_backend/src/task_tracker/test_db.py
new file mode 100644
index 00000000..bc91cb26
--- /dev/null
+++ b/simple_backend/src/task_tracker/test_db.py
@@ -0,0 +1,221 @@
+import pytest
+import json
+from unittest.mock import Mock, patch, MagicMock
+from typing import List, Dict, Any
+from db import CloudDB # Импортируйте ваш реальный модуль
+
+# Фикстуры для тестов
+@pytest.fixture
+def cloud_db():
+ """Создает экземпляр CloudDB для тестирования"""
+ return CloudDB()
+
+@pytest.fixture
+def sample_data():
+ """Пример данных для тестирования"""
+ return {
+ "name": "Test Task",
+ "status": "pending",
+ "description": "Test description"
+ }
+
+@pytest.fixture
+def mock_response():
+ """Мок ответа от API"""
+ def create_mock_response(json_data, status_code=200):
+ mock = Mock()
+ mock.json.return_value = json_data
+ mock.status_code = status_code
+ mock.raise_for_status = Mock()
+ return mock
+ return create_mock_response
+
+# Тесты для CloudDB
+class TestCloudDB:
+
+ @patch('db.requests.get')
+ def test_get_json_success(self, mock_get, cloud_db, mock_response):
+ """Тест успешного получения JSON из облака"""
+ # Мокируем ответ API
+ test_data = {"1": {"name": "test"}, "2": {"name": "test2"}}
+ mock_get.return_value = mock_response(test_data)
+
+ result = cloud_db._CloudDB__get_json()
+
+ assert result == test_data
+ mock_get.assert_called_once_with(
+ "https://api.jsonbin.io/v3/b/68b1c53ed0ea881f406a555a",
+ headers={
+ 'X-Master-Key': '$2a$10$gkEEHNtdmq7FCAUiGF88k.IDmciahKhZmVL7ZBfbANA6US9nsQsu6',
+ 'X-Bin-Meta': 'false'
+ }
+ )
+
+ @patch('db.requests.get')
+ def test_get_json_failure(self, mock_get, cloud_db, mock_response):
+ """Тест обработки ошибки при получении JSON"""
+ mock_get.return_value = mock_response({}, 404)
+ mock_get.return_value.raise_for_status.side_effect = Exception("API Error")
+
+ with pytest.raises(Exception):
+ cloud_db._CloudDB__get_json()
+
+ @patch('db.requests.put')
+ @patch('db.requests.get')
+ def test_add_success(self, mock_get, mock_put, cloud_db, sample_data, mock_response):
+ """Тест успешного добавления элемента"""
+ # Мокируем начальные данные
+ mock_get.return_value = mock_response({})
+ mock_put.return_value = mock_response({}, 200)
+
+ result = cloud_db.add(1, sample_data)
+
+ assert result is True
+ mock_put.assert_called_once()
+
+ @patch('db.requests.get')
+ def test_get_all(self, mock_get, cloud_db, mock_response):
+ """Тест получения всех элементов"""
+ test_data = {"1": {"name": "test1"}, "2": {"name": "test2"}}
+ mock_get.return_value = mock_response(test_data)
+
+ result = cloud_db.get_all()
+
+ assert result == [{"name": "test1"}, {"name": "test2"}]
+ assert len(result) == 2
+
+ @patch('db.requests.get')
+ def test_get_existing_item(self, mock_get, cloud_db, mock_response):
+ """Тест получения существующего элемента"""
+ test_data = {"1": {"name": "test1"}, "2": {"name": "test2"}}
+ mock_get.return_value = mock_response(test_data)
+
+ result = cloud_db.get(1)
+
+ assert result == {"name": "test1"}
+
+ @patch('db.requests.get')
+ def test_get_nonexistent_item(self, mock_get, cloud_db, mock_response):
+ """Тест получения несуществующего элемента"""
+ test_data = {"1": {"name": "test1"}}
+ mock_get.return_value = mock_response(test_data)
+
+ result = cloud_db.get(999)
+
+ assert result is None
+
+ @patch('db.requests.put')
+ @patch('db.requests.get')
+ def test_delete_existing_item(self, mock_get, mock_put, cloud_db, mock_response):
+ """Тест удаления существующего элемента"""
+ test_data = {"1": {"name": "test1"}, "2": {"name": "test2"}}
+ mock_get.return_value = mock_response(test_data)
+ mock_put.return_value = mock_response({}, 200)
+
+ result = cloud_db.delete(1)
+
+ assert result is True
+ mock_put.assert_called_once()
+
+ @patch('db.requests.get')
+ def test_delete_nonexistent_item(self, mock_get, cloud_db, mock_response):
+ """Тест удаления несуществующего элемента"""
+ test_data = {"1": {"name": "test1"}}
+ mock_get.return_value = mock_response(test_data)
+
+ result = cloud_db.delete(999)
+
+ assert result is False
+
+ @patch('db.requests.put')
+ @patch('db.requests.get')
+ def test_update_existing_item(self, mock_get, mock_put, cloud_db, mock_response, sample_data):
+ """Тест обновления существующего элемента"""
+ test_data = {"1": {"name": "old_name"}}
+ mock_get.return_value = mock_response(test_data)
+ mock_put.return_value = mock_response({}, 200)
+
+ result = cloud_db.update(1, sample_data)
+
+ assert result is True
+ mock_put.assert_called_once()
+
+ @patch('db.requests.get')
+ def test_update_nonexistent_item(self, mock_get, cloud_db, mock_response, sample_data):
+ """Тест обновления несуществующего элемента"""
+ test_data = {"1": {"name": "test1"}}
+ mock_get.return_value = mock_response(test_data)
+
+ result = cloud_db.update(999, sample_data)
+
+ assert result is False
+
+ @patch('db.requests.put')
+ @patch('db.requests.get')
+ def test_add_multiple_items(self, mock_get, mock_put, cloud_db, sample_data, mock_response):
+ """Тест добавления нескольких элементов"""
+ mock_get.return_value = mock_response({})
+ mock_put.return_value = mock_response({}, 200)
+
+ # Добавляем несколько элементов
+ cloud_db.add(1, sample_data)
+ cloud_db.add(2, {"name": "second_task"})
+
+ assert mock_put.call_count == 2
+
+ @patch('db.requests.put')
+ @patch('db.requests.get')
+ def test_data_persistence_simulation(self, mock_get, mock_put, cloud_db, sample_data, mock_response):
+ """Тест симуляции сохранения данных"""
+ # Первый вызов - пустые данные
+ mock_get.return_value = mock_response({})
+ mock_put.return_value = mock_response({}, 200)
+
+ cloud_db.add(1, sample_data)
+
+ # Второй вызов - данные должны содержать добавленный элемент
+ test_data_after_add = {"1": sample_data}
+ mock_get.return_value = mock_response(test_data_after_add)
+
+ result = cloud_db.get(1)
+ assert result == sample_data
+
+ @patch('db.requests.put')
+ @patch('db.requests.get')
+ def test_json_serialization(self, mock_get, mock_put, cloud_db, sample_data, mock_response):
+ """Тест корректной сериализации JSON"""
+ mock_get.return_value = mock_response({})
+ mock_put.return_value = mock_response({}, 200)
+
+ cloud_db.add(1, sample_data)
+
+ # Проверяем, что put был вызван с правильными данными
+ call_args = mock_put.call_args
+ assert call_args is not None
+ # Проверяем, что данные были сериализованы в JSON
+ assert '"name": "Test Task"' in call_args[1]['data']
+
+# Тесты для обработки ошибок
+class TestCloudDBErrorHandling:
+
+ @patch('db.requests.get')
+ def test_get_json_network_error(self, mock_get, cloud_db):
+ """Тест обработки сетевой ошибки"""
+ mock_get.side_effect = Exception("Network error")
+
+ with pytest.raises(Exception):
+ cloud_db._CloudDB__get_json()
+
+ @patch('db.requests.put')
+ @patch('db.requests.get')
+ def test_add_network_error(self, mock_get, mock_put, cloud_db, sample_data, mock_response):
+ """Тест обработки сетевой ошибки при добавлении"""
+ mock_get.return_value = mock_response({})
+ mock_put.side_effect = Exception("Network error")
+
+ with pytest.raises(Exception):
+ cloud_db.add(1, sample_data)
+
+# Запуск тестов
+if __name__ == "__main__":
+ pytest.main([__file__, "-v"])
\ No newline at end of file
diff --git a/simple_backend/src/task_tracker/utils.py b/simple_backend/src/task_tracker/utils.py
new file mode 100644
index 00000000..b48c562e
--- /dev/null
+++ b/simple_backend/src/task_tracker/utils.py
@@ -0,0 +1,64 @@
+import json
+import requests
+
+
+class TaskController:
+ def __init__(self, data_base) -> None:
+ self.__data_base = data_base
+
+ def get_all_task(self):
+ return self.__data_base.get_all()
+
+ def get_task(self, id):
+ return self.__data_base.get(id)
+
+ def add_task(self, task):
+ return self.__data_base.add(int(task['id']), task)
+
+ def delete_task(self, id):
+ return self.__data_base.delete(id)
+
+ def update_task(self,id, task):
+ return self.__data_base.update(id, task)
+
+class Worker_AI():
+ def __init__(self) -> None:
+ self.key_api = '7u7cATCBx2R6jt2Qs_z958LzUGGSvquI23LpW-3h'
+ key_bin = 'e8fb4d5fec908c5386b2dc336056ae78'
+ root = 'https://api.cloudflare.com/client/v4/accounts/'
+ self.model = '@cf/meta/llama-3-8b-instruct'
+ self.url = f"{root}{key_bin}/ai/run/{self.model}"
+ self.prompt = """
+ You are an AI task solver. Your role is to provide extremely concise and precise solutions in Russian.
+ **Hard Rule:**
+ The sum of the character count of the original input task AND the character count of your solution MUST be less than 500.
+ **Instructions:**
+ 1. You will receive a task from the user.
+ 2. You MUST calculate the character length of this input task.
+ 3. Your solution must be so concise that its length + the input task's length < 500.
+ 4. Your response MUST NOT repeat the task. Provide only the pure solution.
+
+ Act strictly according to these instructions. If the input task is too long to allow for a solution within the limit, state this clearly.
+ """
+
+ def creating_solution_to_task(self, task):
+ inputs = [{"role": "system", "content": self.prompt},
+ { "role": "user", "content": task}]
+ input = {"messages": inputs}
+ solution = self.__post_json(input)
+ return solution.json()['result']['response']
+
+ def __post_json(self, data):
+ new_data = json.dumps(data)
+
+ headers = {"Authorization": f'Bearer {self.key_api}'}
+
+ req = requests.post(self.url ,data=new_data, headers=headers)
+
+ if req.status_code != 200:
+ raise Exception(f"API Error {req}")
+
+ return req
+
+a = Worker_AI()
+print(a.creating_solution_to_task('Как быстро охладить напиток без холодильника?'))