diff --git a/README.md b/README.md index 33c0486..96054e5 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ The application allows you to automate the process of generating a bibliography Supported citation styles: - ГОСТ Р 7.0.5-2008 +- American Psychological Association (APA) ## Installation diff --git a/docs/source/index.rst b/docs/source/index.rst index f552b0d..025dea5 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -7,6 +7,7 @@ Поддерживаемые стили цитирования: - ГОСТ Р 7.0.5-2008 + - American Psychological Association (АРА) Установка ========= diff --git a/src/apa_renderer.py b/src/apa_renderer.py new file mode 100644 index 0000000..b478c71 --- /dev/null +++ b/src/apa_renderer.py @@ -0,0 +1,54 @@ +""" +Функции для генерации выходного файла с оформленным списком использованных источников. +""" +from __future__ import annotations + +from pathlib import Path + +from docx import Document +from docx.enum.text import WD_ALIGN_PARAGRAPH # pylint: disable=E0611 +from docx.shared import Pt + + +class APARenderer: + """ + Создание выходного файла – Word. + """ + + def __init__(self, rows: tuple[str, ...]): + self.rows = rows + + def render(self, path: Path | str) -> None: + """ + Метод генерации Word-файла со списком использованных источников. + + :param Path | str path: Путь для сохранения выходного файла. + """ + + document = Document() + + # стилизация заголовка + paragraph = document.add_paragraph() + paragraph.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER + runner = paragraph.add_run("Список использованной литературы") + runner.bold = True + + # стилизация текста + style_normal = document.styles["Normal"] + style_normal.font.name = "Times New Roman" + style_normal.font.size = Pt(12) + style_normal.paragraph_format.line_spacing = 1.5 + style_normal.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY + + for row in self.rows: + # добавление источника + content = row.split("/i/") + if len(content) > 1: + list_number = document.add_paragraph(content[0], style="List Number") + list_number.add_run(content[1]).italic = True + list_number.add_run(content[2]).italic = False + else: + document.add_paragraph(row, style="List Number") + + # сохранение файла Word + document.save(path) diff --git a/src/formatters/models.py b/src/formatters/models.py index c9236ca..871ea36 100644 --- a/src/formatters/models.py +++ b/src/formatters/models.py @@ -78,3 +78,57 @@ class ArticlesCollectionModel(BaseModel): publishing_house: str year: int = Field(..., gt=0) pages: str + + +class MagazineArticleModel(BaseModel): + + """ + Модель статьи из журнала: + + .. code-block:: + + MagazineArticleModel( + authors="Иванов И.М., Петров С.Н.", + article_title="Наука как искусство", + magazine_title="Образование и наука", + year=2020, + volume=10, + pages="25-30", + ) + """ + + authors: str + article_title: str + magazine_title: str + year: int = Field(..., gt=0) + volume: int = Field(..., gt=0) + pages: str + + +class ThesisModel(BaseModel): + + """ + Модель диссертации: + + .. code-block:: + + ThesisModel( + author="Иванов И.М.", + title="Наука как искусство", + degree="д-р. / канд.", + field="экон.", + code="01.01.01", + city="СПб.", + year=2020, + pages=199, + ) + """ + + author: str + title: str + degree: str + field: str + code: str + city: str + year: int = Field(..., gt=0) + pages: int = Field(..., gt=0) diff --git a/src/formatters/styles/apa.py b/src/formatters/styles/apa.py new file mode 100644 index 0000000..507baba --- /dev/null +++ b/src/formatters/styles/apa.py @@ -0,0 +1,93 @@ +""" +Стиль цитирования по American Psychological Association. +""" +from string import Template + +from pydantic import BaseModel + +from formatters.models import InternetResourceModel, MagazineArticleModel +from formatters.styles.base import BaseCitationStyle +from logger import get_logger + +logger = get_logger(__name__) + + +class APAInternetResource(BaseCitationStyle): + """ + Форматирование для интернет-ресурсов. + """ + + data: InternetResourceModel + + @property + def template(self) -> Template: + return Template("/i/$article/i/. ($access_date). $website. $link") + + def substitute(self) -> str: + logger.info('Форматирование интернет-ресурса "%s" ...', self.data.article) + + return self.template.substitute( + article=self.data.article, + website=self.data.website, + link=self.data.link, + access_date=self.data.access_date, + ) + + +class APAMagazineArticle(BaseCitationStyle): + """ + Форматирование для статьи из журнала. + """ + + data: MagazineArticleModel + + @property + def template(self) -> Template: + return Template( + "$authors ($year). $article_title. /i/$magazine_title/i/, $volume, $pages." + ) + + def substitute(self) -> str: + + logger.info('Форматирование статьи из журнала "%s" ...', self.data.article_title) + + return self.template.substitute( + authors=self.data.authors, + article_title=self.data.article_title, + magazine_title=self.data.magazine_title, + year=self.data.year, + volume=self.data.volume, + pages=self.data.pages, + ) + + +class APACitationFormatter: + """ + Базовый класс для итогового форматирования списка источников. + """ + + formatters_map = { + MagazineArticleModel.__name__: APAMagazineArticle, + InternetResourceModel.__name__: APAInternetResource, + } + + def __init__(self, models: list[BaseModel]) -> None: + """ + Конструктор. + :param models: Список объектов для форматирования + """ + + formatted_items = [] + for model in models: + formatted_items.append(self.formatters_map.get(type(model).__name__)(model)) # type: ignore + + self.formatted_items = formatted_items + + def format(self) -> list[BaseCitationStyle]: + """ + Форматирование списка источников. + + :return: + """ + + return sorted(self.formatted_items, key=lambda item: item.formatted) diff --git a/src/formatters/styles/gost.py b/src/formatters/styles/gost.py index b237f8a..86a523e 100644 --- a/src/formatters/styles/gost.py +++ b/src/formatters/styles/gost.py @@ -5,11 +5,16 @@ from pydantic import BaseModel -from formatters.models import BookModel, InternetResourceModel, ArticlesCollectionModel +from formatters.models import ( + BookModel, + InternetResourceModel, + ArticlesCollectionModel, + MagazineArticleModel, + ThesisModel +) from formatters.styles.base import BaseCitationStyle from logger import get_logger - logger = get_logger(__name__) @@ -27,7 +32,6 @@ def template(self) -> Template: ) def substitute(self) -> str: - logger.info('Форматирование книги "%s" ...', self.data.title) return self.template.substitute( @@ -64,7 +68,6 @@ def template(self) -> Template: ) def substitute(self) -> str: - logger.info('Форматирование интернет-ресурса "%s" ...', self.data.article) return self.template.substitute( @@ -89,7 +92,6 @@ def template(self) -> Template: ) def substitute(self) -> str: - logger.info('Форматирование сборника статей "%s" ...', self.data.article_title) return self.template.substitute( @@ -103,6 +105,60 @@ def substitute(self) -> str: ) +class GOSTMagazineArticle(BaseCitationStyle): + """ + Форматирование для статьи из журнала. + """ + + data: MagazineArticleModel + + @property + def template(self) -> Template: + return Template( + "$authors $article_title // $magazine_title. $year. Т. $volume. С. $pages." + ) + + def substitute(self) -> str: + logger.info('Форматирование статьи из журнала "%s" ...', self.data.article_title) + + return self.template.substitute( + authors=self.data.authors, + article_title=self.data.article_title, + magazine_title=self.data.magazine_title, + year=self.data.year, + volume=self.data.volume, + pages=self.data.pages, + ) + + +class GOSTThesis(BaseCitationStyle): + """ + Форматирование для диссертации. + """ + + data: ThesisModel + + @property + def template(self) -> Template: + return Template( + "$author $title : $degree $field $code / $city, $year. - $pages с." + ) + + def substitute(self) -> str: + logger.info('Форматирование диссертации "%s" ...', self.data.title) + + return self.template.substitute( + author=self.data.author, + title=self.data.title, + degree=self.data.degree, + field=self.data.field, + code=self.data.code, + city=self.data.city, + year=self.data.year, + pages=self.data.pages, + ) + + class GOSTCitationFormatter: """ Базовый класс для итогового форматирования списка источников. @@ -112,6 +168,8 @@ class GOSTCitationFormatter: BookModel.__name__: GOSTBook, InternetResourceModel.__name__: GOSTInternetResource, ArticlesCollectionModel.__name__: GOSTCollectionArticle, + MagazineArticleModel.__name__: GOSTMagazineArticle, + ThesisModel.__name__: GOSTThesis } def __init__(self, models: list[BaseModel]) -> None: diff --git a/src/main.py b/src/main.py index 7a9fa8e..2688f05 100644 --- a/src/main.py +++ b/src/main.py @@ -6,9 +6,12 @@ import click from formatters.styles.gost import GOSTCitationFormatter +from formatters.styles.apa import APACitationFormatter from logger import get_logger from readers.reader import SourcesReader +from readers.apa_reader import APASourcesReader from renderer import Renderer +from apa_renderer import APARenderer from settings import INPUT_FILE_PATH, OUTPUT_FILE_PATH logger = get_logger(__name__) @@ -30,7 +33,7 @@ class CitationEnum(Enum): "--citation", "-c", "citation", - type=click.Choice([item.name for item in CitationEnum], case_sensitive=False), + type=click.Choice(list(CitationEnum), case_sensitive=False), default=CitationEnum.GOST.name, show_default=True, help="Стиль цитирования", @@ -75,16 +78,22 @@ def process_input( path_input, path_output, ) - - models = SourcesReader(path_input).read() - formatted_models = tuple( - str(item) for item in GOSTCitationFormatter(models).format() - ) - - logger.info("Генерация выходного файла ...") - Renderer(formatted_models).render(path_output) - - logger.info("Команда успешно завершена.") + if citation is CitationEnum.APA.name: + models = APASourcesReader(path_input).read() + formatted_models = tuple( + str(item) for item in APACitationFormatter(models).format() + ) + logger.info("Генерация выходного файла ...") + APARenderer(formatted_models).render(path_output) + logger.info("Команда успешно завершена.") + else: + models = SourcesReader(path_input).read() + formatted_models = tuple( + str(item) for item in GOSTCitationFormatter(models).format() + ) + logger.info("Генерация выходного файла ...") + Renderer(formatted_models).render(path_output) + logger.info("Команда успешно завершена.") if __name__ == "__main__": diff --git a/src/readers/apa_reader.py b/src/readers/apa_reader.py new file mode 100644 index 0000000..4a69737 --- /dev/null +++ b/src/readers/apa_reader.py @@ -0,0 +1,96 @@ +""" +Чтение исходного файла. +""" +from datetime import date +from typing import Type + +import openpyxl +from openpyxl.workbook import Workbook + +from formatters.models import InternetResourceModel, MagazineArticleModel +from logger import get_logger +from readers.base import BaseReader + +logger = get_logger(__name__) + + +class InternetResourceReader(BaseReader): + """ + Чтение модели интернет-ресурса. + """ + + @property + def model(self) -> Type[InternetResourceModel]: + return InternetResourceModel + + @property + def sheet(self) -> str: + return "Интернет-ресурс" + + @property + def attributes(self) -> dict: + return { + "article": {0: str}, + "website": {1: str}, + "link": {2: str}, + "access_date": {3: date}, + } + + +class MagazineArticleReader(BaseReader): + """ + Чтение модели книги. + """ + + @property + def model(self) -> Type[MagazineArticleModel]: + return MagazineArticleModel + + @property + def sheet(self) -> str: + return "Статья из журнала" + + @property + def attributes(self) -> dict: + return { + "authors": {0: str}, + "article_title": {1: str}, + "magazine_title": {2: str}, + "year": {3: int}, + "volume": {4: int}, + "pages": {5: str}, + } + + +class APASourcesReader: + """ + Чтение из источника данных. + """ + + # зарегистрированные читатели + readers = [ + InternetResourceReader, + MagazineArticleReader + ] + + def __init__(self, path: str) -> None: + """ + Конструктор. + :param path: Путь к исходному файлу для чтения. + """ + + logger.info("Загрузка рабочей книги ...") + self.workbook: Workbook = openpyxl.load_workbook(path) + + def read(self) -> list: + """ + Чтение исходного файла. + :return: Список прочитанных моделей (строк). + """ + + items = [] + for reader in self.readers: + logger.info("Чтение %s ...", reader) + items.extend(reader(self.workbook).read()) # type: ignore + + return items diff --git a/src/readers/reader.py b/src/readers/reader.py index 9007a80..4f75b46 100644 --- a/src/readers/reader.py +++ b/src/readers/reader.py @@ -7,7 +7,7 @@ import openpyxl from openpyxl.workbook import Workbook -from formatters.models import BookModel, InternetResourceModel, ArticlesCollectionModel +from formatters.models import BookModel, InternetResourceModel, ArticlesCollectionModel, MagazineArticleModel, ThesisModel from logger import get_logger from readers.base import BaseReader @@ -90,6 +90,58 @@ def attributes(self) -> dict: } +class MagazineArticleReader(BaseReader): + """ + Чтение модели статьи из журнала + """ + + @property + def model(self) -> Type[MagazineArticleModel]: + return MagazineArticleModel + + @property + def sheet(self) -> str: + return "Статья из журнала" + + @property + def attributes(self) -> dict: + return { + "authors": {0: str}, + "article_title": {1: str}, + "magazine_title": {2: str}, + "year": {3: int}, + "volume": {4: int}, + "pages": {5: str}, + } + + +class ThesisReader(BaseReader): + """ + Чтение модели диссертации + """ + + @property + def model(self) -> Type[ThesisModel]: + return ThesisModel + + @property + def sheet(self) -> str: + return "Диссертация" + + @property + def attributes(self) -> dict: + return { + "author": {0: str}, + "title": {1: str}, + "degree": {2: str}, + "field": {3: str}, + "code": {4: date}, + "city": {5: str}, + "year": {6: int}, + "pages": {7: int}, + } + + class SourcesReader: """ Чтение из источника данных. @@ -100,6 +152,8 @@ class SourcesReader: BookReader, InternetResourceReader, ArticlesCollectionReader, + MagazineArticleReader, + ThesisReader ] def __init__(self, path: str) -> None: diff --git a/src/tests/conftest.py b/src/tests/conftest.py index ac5c9aa..3275f07 100644 --- a/src/tests/conftest.py +++ b/src/tests/conftest.py @@ -3,7 +3,13 @@ """ import pytest -from formatters.models import BookModel, InternetResourceModel, ArticlesCollectionModel +from formatters.models import ( + BookModel, + InternetResourceModel, + ArticlesCollectionModel, + MagazineArticleModel, + ThesisModel +) @pytest.fixture @@ -58,3 +64,41 @@ def articles_collection_model_fixture() -> ArticlesCollectionModel: year=2020, pages="25-30", ) + + +@pytest.fixture +def magazine_article_model_fixture() -> MagazineArticleModel: + """ + Фикстура модели статьи из журнала + + :return: MagazineArticleModel + """ + + return MagazineArticleModel( + authors="Иванов И.М., Петров С.Н.", + article_title="Наука как искусство", + magazine_title="Образование и наука", + year=2020, + volume=10, + pages="25-30", + ) + + +@pytest.fixture +def thesis_model_fixture() -> ThesisModel: + """ + Фикстура модели диссертации + + :return: ThesisModel + """ + + return ThesisModel( + author="Иванов И.М.", + title="Наука как искусство", + degree="д-р. / канд.", + field="экон.", + code="01.01.01", + city="СПб.", + year=2020, + pages=199, + ) diff --git a/src/tests/formatters/test_apa.py b/src/tests/formatters/test_apa.py new file mode 100644 index 0000000..09702b3 --- /dev/null +++ b/src/tests/formatters/test_apa.py @@ -0,0 +1,76 @@ +""" +Тестирование функций оформления списка источников по ГОСТ Р 7.0.5-2008. +""" + +from formatters.base import BaseCitationFormatter +from formatters.models import ( + InternetResourceModel, + MagazineArticleModel, +) +from formatters.styles.apa import ( + APAInternetResource, + APAMagazineArticle, +) + + +class TestAPA: + """ + Тестирование оформления списка источников согласно ГОСТ Р 7.0.5-2008. + """ + + def test_internet_resource( + self, internet_resource_model_fixture: InternetResourceModel + ) -> None: + """ + Тестирование форматирования интернет-ресурса. + + :param InternetResourceModel internet_resource_model_fixture: Фикстура модели интернет-ресурса + :return: + """ + + model = APAInternetResource(internet_resource_model_fixture) + + assert ( + model.formatted + == "/i/Наука как искусство/i/. (01.01.2021). Ведомости. https://www.vedomosti.ru" + ) + + def test_magazine_article( + self, magazine_article_model_fixture: MagazineArticleModel + ) -> None: + """ + Тестирование форматирования статьи из журнала. + + :param MagazineArticleModel magazine_article_model_fixture: Фикстура модели статьи из журнала + :return: + """ + + model = APAMagazineArticle(magazine_article_model_fixture) + + assert ( + model.formatted + == "Иванов И.М., Петров С.Н. (2020). Наука как искусство. /i/Образование и наука/i/, 10, 25-30." + ) + + def test_citation_formatter( + self, + internet_resource_model_fixture: InternetResourceModel, + magazine_article_model_fixture: MagazineArticleModel, + ) -> None: + """ + Тестирование функции итогового форматирования списка источников. + + :param InternetResourceModel internet_resource_model_fixture: Фикстура модели интернет-ресурса + :param MagazineArticleModel magazine_article_model_fixture: Фикстура модели статьи из журнала + :return: + """ + + models = [ + APAInternetResource(internet_resource_model_fixture), + APAMagazineArticle(magazine_article_model_fixture), + ] + result = BaseCitationFormatter(models).format() + + # тестирование сортировки списка источников + assert result[0] == models[0] + assert result[1] == models[1] diff --git a/src/tests/formatters/test_gost.py b/src/tests/formatters/test_gost.py index c93e1e7..4161dc8 100644 --- a/src/tests/formatters/test_gost.py +++ b/src/tests/formatters/test_gost.py @@ -3,9 +3,20 @@ """ from formatters.base import BaseCitationFormatter -from formatters.models import BookModel, InternetResourceModel, ArticlesCollectionModel -from formatters.styles.gost import GOSTBook, GOSTInternetResource, GOSTCollectionArticle - +from formatters.models import ( + BookModel, + InternetResourceModel, + ArticlesCollectionModel, + MagazineArticleModel, + ThesisModel +) +from formatters.styles.gost import ( + GOSTBook, + GOSTInternetResource, + GOSTCollectionArticle, + GOSTMagazineArticle, + GOSTThesis +) class TestGOST: """ @@ -61,11 +72,47 @@ def test_articles_collection( == "Иванов И.М., Петров С.Н. Наука как искусство // Сборник научных трудов. – СПб.: АСТ, 2020. – С. 25-30." ) + def test_magazine_article( + self, magazine_article_model_fixture: MagazineArticleModel + ) -> None: + """ + Тестирование форматирования статьи из журнала. + + :param MagazineArticleModel magazine_article_model_fixture: Фикстура модели статьи из журнала + :return: + """ + + model = GOSTMagazineArticle(magazine_article_model_fixture) + + assert ( + model.formatted + == "Иванов И.М., Петров С.Н. Наука как искусство // Образование и наука. 2020. Т. 10. С. 25-30." + ) + + def test_thesis( + self, thesis_model_fixture: ThesisModel + ) -> None: + """ + Тестирование форматирования диссертации. + + :param ThesisModel thesis_model_fixture: Фикстура модели диссертации + :return: + """ + + model = GOSTThesis(thesis_model_fixture) + + assert ( + model.formatted + == "Иванов И.М. Наука как искусство : д-р. / канд. экон. 01.01.01 / СПб., 2020. - 199 с." + ) + def test_citation_formatter( self, book_model_fixture: BookModel, internet_resource_model_fixture: InternetResourceModel, articles_collection_model_fixture: ArticlesCollectionModel, + magazine_article_model_fixture: MagazineArticleModel, + thesis_model_fixture: ThesisModel ) -> None: """ Тестирование функции итогового форматирования списка источников. @@ -73,6 +120,8 @@ def test_citation_formatter( :param BookModel book_model_fixture: Фикстура модели книги :param InternetResourceModel internet_resource_model_fixture: Фикстура модели интернет-ресурса :param ArticlesCollectionModel articles_collection_model_fixture: Фикстура модели сборника статей + :param MagazineArticleModel magazine_article_model_fixture: Фикстура модели статьи из журнала + :param ThesisModel thesis_model_fixture: Фикстура модели диссертации :return: """ @@ -80,10 +129,14 @@ def test_citation_formatter( GOSTBook(book_model_fixture), GOSTInternetResource(internet_resource_model_fixture), GOSTCollectionArticle(articles_collection_model_fixture), + GOSTMagazineArticle(magazine_article_model_fixture), + GOSTThesis(thesis_model_fixture) ] result = BaseCitationFormatter(models).format() # тестирование сортировки списка источников - assert result[0] == models[2] - assert result[1] == models[0] - assert result[2] == models[1] + assert result[0] == models[4] + assert result[1] == models[3] + assert result[2] == models[2] + assert result[3] == models[0] + assert result[4] == models[1] diff --git a/src/tests/readers/test_apa_readers.py b/src/tests/readers/test_apa_readers.py new file mode 100644 index 0000000..2462cc4 --- /dev/null +++ b/src/tests/readers/test_apa_readers.py @@ -0,0 +1,96 @@ +""" +Тестирование функций чтения данных из источника. +""" +from typing import Any + +import pytest + +from formatters.models import ( + InternetResourceModel, + MagazineArticleModel, +) +from readers.apa_reader import ( + APASourcesReader, + InternetResourceReader, + MagazineArticleReader, +) +from settings import TEMPLATE_FILE_PATH + + +class TestReaders: + """ + Тестирование функций чтения данных из источника. + """ + + @pytest.fixture + def workbook(self) -> Any: + """ + Получение объекта тестовой рабочей книги. + :return: + """ + + return APASourcesReader(TEMPLATE_FILE_PATH).workbook + + def test_internet_resource(self, workbook: Any) -> None: + """ + Тестирование чтения интернет-ресурса. + + :param workbook: Объект тестовой рабочей книги. + """ + + models = InternetResourceReader(workbook).read() + + assert len(models) == 3 + model = models[0] + + model_type = InternetResourceModel + + assert isinstance(model, model_type) + assert model.article == "Наука как искусство" + assert model.website == "Ведомости" + assert model.link == "https://www.vedomosti.ru" + assert model.access_date == "01.01.2021" + + # проверка общего количества атрибутов + assert len(model_type.schema().get("properties", {}).keys()) == 4 + + def test_magazine_article(self, workbook: Any) -> None: + """ + Тестирование чтения статьи из журнала. + + :param workbook: Объект тестовой рабочей книги. + """ + + models = MagazineArticleReader(workbook).read() + + assert len(models) == 1 + model = models[0] + + model_type = MagazineArticleModel + + assert isinstance(model, model_type) + assert model.authors == "Иванов И.М., Петров С.Н." + assert model.article_title == "Наука как искусство" + assert model.magazine_title == "Образование и наука" + assert model.year == 2020 + assert model.volume == 10 + assert model.pages == "25-30" + + # проверка общего количества атрибутов + assert len(model_type.schema().get("properties", {}).keys()) == 6 + + def test_sources_reader(self) -> None: + """ + Тестирование функции чтения всех моделей из источника. + """ + + models = APASourcesReader(TEMPLATE_FILE_PATH).read() + # проверка общего считанного количества моделей + assert len(models) == 4 + + # проверка наличия всех ожидаемых типов моделей среди типов считанных моделей + model_types = {model.__class__.__name__ for model in models} + assert model_types == { + InternetResourceModel.__name__, + MagazineArticleModel.__name__, + } diff --git a/src/tests/readers/test_readers.py b/src/tests/readers/test_readers.py index 67d863b..88bbf1c 100644 --- a/src/tests/readers/test_readers.py +++ b/src/tests/readers/test_readers.py @@ -5,12 +5,20 @@ import pytest -from formatters.models import BookModel, InternetResourceModel, ArticlesCollectionModel +from formatters.models import ( + BookModel, + InternetResourceModel, + ArticlesCollectionModel, + MagazineArticleModel, + ThesisModel +) from readers.reader import ( BookReader, SourcesReader, InternetResourceReader, ArticlesCollectionReader, + MagazineArticleReader, + ThesisReader ) from settings import TEMPLATE_FILE_PATH @@ -104,6 +112,58 @@ def test_articles_collection(self, workbook: Any) -> None: # проверка общего количества атрибутов assert len(model_type.schema().get("properties", {}).keys()) == 7 + def test_magazine_article(self, workbook: Any) -> None: + """ + Тестирование чтения статьи из журнала. + + :param workbook: Объект тестовой рабочей книги. + """ + + models = MagazineArticleReader(workbook).read() + + assert len(models) == 1 + model = models[0] + + model_type = MagazineArticleModel + + assert isinstance(model, model_type) + assert model.authors == "Иванов И.М., Петров С.Н." + assert model.article_title == "Наука как искусство" + assert model.magazine_title == "Образование и наука" + assert model.year == 2020 + assert model.volume == 10 + assert model.pages == "25-30" + + # проверка общего количества атрибутов + assert len(model_type.schema().get("properties", {}).keys()) == 6 + + def test_thesis(self, workbook: Any) -> None: + """ + Тестирование чтения диссертации. + + :param workbook: Объект тестовой рабочей книги. + """ + + models = ThesisReader(workbook).read() + + assert len(models) == 1 + model = models[0] + + model_type = ThesisModel + + assert isinstance(model, model_type) + assert model.author == "Иванов И.М." + assert model.title == "Наука как искусство" + assert model.degree == "д-р. / канд." + assert model.field == "экон." + assert model.code == "01.01.01" + assert model.city == "СПб." + assert model.year == 2020 + assert model.pages == 199 + + # проверка общего количества атрибутов + assert len(model_type.schema().get("properties", {}).keys()) == 8 + def test_sources_reader(self) -> None: """ Тестирование функции чтения всех моделей из источника. @@ -111,7 +171,7 @@ def test_sources_reader(self) -> None: models = SourcesReader(TEMPLATE_FILE_PATH).read() # проверка общего считанного количества моделей - assert len(models) == 8 + assert len(models) == 10 # проверка наличия всех ожидаемых типов моделей среди типов считанных моделей model_types = {model.__class__.__name__ for model in models} @@ -119,4 +179,6 @@ def test_sources_reader(self) -> None: BookModel.__name__, InternetResourceModel.__name__, ArticlesCollectionModel.__name__, + MagazineArticleModel.__name__, + ThesisModel.__name__, } diff --git a/src/tests/test_apa_renderer.py b/src/tests/test_apa_renderer.py new file mode 100644 index 0000000..7945176 --- /dev/null +++ b/src/tests/test_apa_renderer.py @@ -0,0 +1,43 @@ +""" +Тестирование функций генерации выходного файла. +""" +from pathlib import Path + +import pytest + +from apa_renderer import APARenderer + + +class TestRenderer: + """ + Тестирование функций генерации выходного файла. + """ + + @pytest.fixture + def formatted_models(self) -> tuple[str, ...]: + """ + Получение объекта тестовой рабочей книги. + :return: + """ + + return ( + "Строка №1", + "Строка №2", + "Строка №3", + ) + + def test_render(self, tmp_path: Path, formatted_models: tuple[str, ...]) -> None: + """ + Тестирование функции генерации выходного файла. + + :param Path tmp_path: Фикстура пути для временного хранения файла во время тестирования + :param tuple[str, ...] formatted_models: Список строк для сохранения в файле + """ + + path = tmp_path / "output.docx" + APARenderer(formatted_models).render(path) + + # проверка наличия файла + assert len(list(tmp_path.iterdir())) == 1 + # проверка размера файла в байтах на диске + assert path.stat().st_size == 36773