Skip to content

Commit 57e19e5

Browse files
authored
Merge pull request #51 from zezOtik/Savvateev_lab_3
LAB3
2 parents 738b00c + 3626a1b commit 57e19e5

30 files changed

+943
-14
lines changed

.github/workflows/sae-lab3.yml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
name: SAE Lab3 Tests
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- 'students_folder/savandr/**'
7+
- 'tests/Savvateev/**'
8+
- 'tests/src/Savvateev/**'
9+
workflow_dispatch:
10+
11+
jobs:
12+
sae-lab3-tests:
13+
name: Run SAE Lab3 Tests
14+
runs-on: ubuntu-latest
15+
16+
steps:
17+
- name: Checkout repository
18+
uses: actions/checkout@v4
19+
20+
- name: Set up Python
21+
uses: actions/setup-python@v4
22+
with:
23+
python-version: "3.13"
24+
25+
- name: Cache pip packages
26+
uses: actions/cache@v3
27+
with:
28+
path: ~/.cache/pip
29+
key: ${{ runner.os }}-pip-${{ hashFiles('/pyproject.toml', '**/tox.ini') }}
30+
restore-keys: |
31+
${{ runner.os }}-pip-
32+
33+
- name: Install tox
34+
run: |
35+
python -m pip install --upgrade pip
36+
pip install tox
37+
38+
- name: Run SAE Lab3 Tests
39+
run: tox -e sae_lab3
40+
41+
- name: Show tox environments
42+
if: failure()
43+
run: tox list
44+
45+
- name: Show test files found
46+
if: failure()
47+
run: |
48+
echo "=== Test files with sae_lab3 marker ==="
49+
find tests/ -name "*.py" -exec grep -l "sae_lab3" {} \; || true
50+
echo "=== All test files ==="
51+
find tests/ -name "test_*.py" || true

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ dependencies = [
1919
]
2020

2121
[tool.setuptools]
22-
packages = ["students_folder"]
2322
include-package-data = true
2423

25-
[tool.setuptools.package-dir]
26-
"" = "."
24+
[tool.setuptools.packages.find]
25+
where = ["."]
26+
include = ["students_folder*", "tests*"]
2727

2828
[tool.setuptools.package-data]
2929
"*" = ["*.yaml", "*.yml", "*.txt", "*.md"]

students_folder/savandr/__init__.py

Whitespace-only changes.

students_folder/savandr/lab_2/__init__.py

Whitespace-only changes.

students_folder/savandr/lab_2/lab_2.py

Lines changed: 239 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,36 +3,127 @@
33
)
44
from typing import List, Union, Optional, Literal, Annotated
55

6-
RussianStr = Annotated[str, Field(pattern=r'^[А-Яа-яЁё\s\-]+$')]
6+
RussianStr = Annotated[str, Field(pattern=r'^[А-Яа-яЁё\s\-0-9]+$')]
77

88
class UserSpec(BaseModel):
9+
"""
10+
Модель профиля пользователя.
11+
12+
Атрибуты:
13+
- user_id (int): Идентификатор пользователя.
14+
- username (str): Имя пользователя, состоящее из русских букв, пробелов, дефисов и цифр.
15+
- surname (str): Фамилия пользователя, состоящая из русских букв, пробелов, дефисов и цифр.
16+
- second_name (str, optional): Отчество пользователя, опционально.
17+
- email (str): Электронная почта пользователя, должна содержать '@' и '.'.
18+
- status (Literal['active', 'non-active']): Статус пользователя, может быть 'active' или 'non-active'.
19+
20+
Конфигурация модели:
21+
- `extra='forbid'`: Запрещает наличие дополнительных полей, не указанных в модели.
22+
23+
Валидаторы:
24+
- `validate_email`: Проверяет, что email содержит символы '@' и '.'.
25+
- username и surname должны состоять только из русских букв, пробелов, дефисов и цифр.
26+
- second_name может быть None, если не указано.
27+
- status должен быть либо 'active', либо 'non-active'.
28+
29+
Пример использования:
30+
>>> user = UserSpec(
31+
... user_id=1,
32+
... username='Андрей',
33+
... surname='Савватеев',
34+
... second_name='Эдуардович',
35+
... email='deez@nu.ts',
36+
... status='active'
37+
... )
38+
>>> print(user)
39+
user_id=1 username='Андрей' surname='Савватеев' second_name='Эдуардович' email='deez@nu.ts' status='active'
40+
"""
41+
942
model_config = ConfigDict(extra='forbid')
1043
user_id: int
1144
username: RussianStr
1245
surname: RussianStr
13-
second_name: Optional[str]
46+
second_name: Optional[str] = None
1447
email: str
1548
status: Literal['active', 'non-active']
1649

1750
@field_validator('email', mode='after')
18-
def validate_email(self, value):
51+
@classmethod
52+
def validate_email(cls, value):
1953
if '@' not in value or '.' not in value:
2054
raise ValueError("Email must contain '@' and '.'")
2155
return value
2256

2357

2458
class ProfileSpec(UserSpec):
59+
"""Модель профиля пользователя с дополнительными полями.
60+
Наследуется от UserSpec(user_id, username, surname, second_name, email, status) и добавляет поля `bio` и `url`.
61+
62+
Атрибуты:
63+
- bio (RussianStr): Краткая биография пользователя, состоящая из русских букв, пробелов, дефисов и цифр.
64+
- url (HttpUrl): URL пользователя, должен содержать '://'.
65+
66+
Конфигурация модели:
67+
- `extra='forbid'`: Запрещает наличие дополнительных полей, не указанных в модели.
68+
69+
Валидаторы:
70+
- `validate_url`: Проверяет, что URL содержит '://'.
71+
- `validate_bio`: Проверяет, что биография состоит только из русских букв, пробелов, дефисов и цифр.
72+
73+
Пример использования:
74+
>>> profile = ProfileSpec(
75+
... user_id=1,
76+
... username='Андрей',
77+
... surname='Савватеев',
78+
... second_name='Эдуардович',
79+
... email='deez@nu.ts',
80+
... status='active',
81+
... bio='Программист и разработчик',
82+
... url='https://example.com/profile'
83+
... )
84+
>>> print(profile)
85+
user_id=1 username='Андрей' surname='Савватеев' second_name='Эдуардович' email='deez@nu.ts' status='active' bio='Программист и разработчик' url='https://example.com/profile'
86+
"""
87+
2588
bio: RussianStr
2689
url: HttpUrl
2790

2891
@field_validator('url', mode='after')
29-
def validate_url(self, value):
30-
if '://' not in value:
92+
@classmethod
93+
def validate_url(cls, value):
94+
if '://' not in str(value):
3195
raise ValueError("URL must contain '://'")
3296
return value
3397

3498

3599
class ItemSpec(BaseModel):
100+
"""
101+
Модель спецификации товара.
102+
103+
Атрибуты:
104+
- item_id (int): Идентификатор товара.
105+
- name (str): Название товара, состоящее из русских букв, пробелов, дефисов и цифр.
106+
- desc (str): Описание товара, состоящее из русских букв, пробелов, дефисов и цифр.
107+
- price (float): Цена товара, должна быть больше 0.
108+
109+
Конфигурация модели:
110+
- `extra='forbid'`: Запрещает наличие дополнительных полей, не указанных в модели.
111+
112+
Валидаторы:
113+
- `validate_price`: Проверяет, что цена товара больше 0.
114+
- name и desc должны состоять только из русских букв, пробелов, дефисов и цифр.
115+
116+
Пример использования:
117+
>>> item = ItemSpec(
118+
... item_id=1,
119+
... name='Товар 1',
120+
... desc='Описание товара 1',
121+
... price=100.0
122+
... )
123+
>>> print(item)
124+
item_id=1 name='Товар 1' desc='Описание товара 1' price=100.0
125+
"""
126+
36127
model_config = {
37128
"extra": "forbid"
38129
}
@@ -41,41 +132,100 @@ class ItemSpec(BaseModel):
41132
desc: RussianStr
42133
price: float
43134

44-
@field_validator('price', mode='after')
45-
def validate_price(self, value):
135+
@field_validator("price", mode="after")
136+
@classmethod
137+
def validate_price(cls, value: float) -> float:
46138
if value <= 0:
47139
raise ValueError("Price must be greater than 0")
48140
return value
49141

50142

51143
class ServiceSpec(BaseModel):
144+
"""
145+
Модель услуги, которую предоставляет магазин.
146+
147+
Атрибуты:
148+
- service_id (int): Идентификатор услуги.
149+
- name (str): Название услуги, состоящее из русских букв, пробелов, дефисов и цифр.
150+
- desc (str): Описание услуги, состоящее из русских букв, пробелов, дефисов и цифр.
151+
- price (float): Цена услуги, должна быть больше 0.
152+
153+
Конфигурация модели:
154+
- `extra='forbid'`: Запрещает наличие дополнительных полей, не указанных в модели.
155+
156+
Валидаторы:
157+
- `validate_price`: Проверяет, что цена услуги больше 0.
158+
- name и desc должны состоять только из русских букв, пробелов, дефисов и цифр.
159+
160+
Пример использования:
161+
>>> service = ServiceSpec(
162+
... service_id=1,
163+
... name='Услуга 1',
164+
... desc='Описание услуги 1',
165+
... price=100.0
166+
... )
167+
>>> print(service)
168+
service_id=1 name='Услуга 1' desc='Описание услуги 1' price=100.0
169+
"""
170+
52171
model_config = {
53-
"extra": "forbid"
172+
"extra": "forbid",
173+
"populate_by_name": True
54174
}
55175
service_id: int
56176
name: RussianStr
57177
desc: RussianStr
58178
price: float
59179

60180
@field_validator('price', mode='after')
61-
def validate_price(self, value):
181+
@classmethod
182+
def validate_price(cls, value):
62183
if value <= 0:
63184
raise ValueError("Price must be greater than 0")
64185
return value
65186

66187

67188
class OrderLineSpec(BaseModel):
189+
"""
190+
Модель строки заказа, которая содержит информацию о товаре или услуге в заказе.
191+
192+
Атрибуты:
193+
- order_id (int): Идентификатор заказа.
194+
- order_line_id (int): Идентификатор строки заказа, должен быть больше 0 и меньше или равен order_id.
195+
- item_line (Union[ServiceSpec, ItemSpec]): Товар или услуга, которая входит в строку заказа.
196+
- quantity (float): Количество товара или услуги в строке заказа, должно быть больше 0.
197+
198+
Конфигурация модели:
199+
- `extra='forbid'`: Запрещает наличие дополнительных полей, не указанных в модели.
200+
201+
Валидаторы:
202+
- `validate_quantity`: Проверяет, что количество больше 0.
203+
- `validate_order_line_id`: Проверяет, что order_line_id больше 0 и меньше или равен order_id.
204+
- `line_price`: Вычисляет цену строки заказа как произведение quantity и цены товара или услуги.
205+
206+
Пример использования:
207+
>>> item = ItemSpec(item_id=1, name='Товар 1', desc='Описание товара 1', price=100.0)
208+
>>> order_line = OrderLineSpec(
209+
... order_id=1,
210+
... order_line_id=1,
211+
... item_line=item,
212+
... quantity=2.0
213+
... )
214+
>>> print(order_line)
215+
order_id=1 order_line_id=1 item_line=ItemSpec(item_id=1, name='Товар 1', desc='Описание товара 1', price=100.0) quantity=2.0 line_price=200.0
216+
"""
217+
68218
model_config = {
69219
"extra": "forbid"
70220
}
71221
order_id: int
72222
order_line_id: int
73223
item_line: Union[ServiceSpec, ItemSpec]
74224
quantity: float
75-
line_price: float
76225

77226
@field_validator('quantity', mode='after')
78-
def validate_quantity(self, value):
227+
@classmethod
228+
def validate_quantity(cls, value):
79229
if value <= 0:
80230
raise ValueError("Quantity must be greater than 0")
81231
return value
@@ -92,6 +242,48 @@ def line_price(self) -> float:
92242

93243

94244
class OrderSpec(BaseModel):
245+
""""
246+
Модель заказа, которая содержит информацию о заказе пользователя.
247+
248+
Атрибуты:
249+
- order_id (int): Идентификатор заказа.
250+
- user_info (ProfileSpec): Профиль пользователя, который сделал заказ.
251+
- items_line (List[OrderLineSpec]): Список строк заказа, каждая из которых содержит информацию о товаре или услуге.
252+
253+
Конфигурация модели:
254+
- `extra='forbid'`: Запрещает наличие дополнительных полей, не указанных в модели.
255+
256+
Валидаторы:
257+
- `validate_order_id`: Проверяет, что order_id больше 0.
258+
- `validate_items_line`: Проверяет, что items_line не пустой и содержит только строки заказа с корректными order_id и order_line_id.
259+
260+
Пример использования:
261+
>>> user_profile = ProfileSpec(
262+
... user_id=1,
263+
... username='Андрей',
264+
... surname='Савватеев',
265+
... second_name='Эдуардович',
266+
... email='andrey.savvateev@example.com',
267+
... status='active',
268+
... bio='Программист и разработчик',
269+
... url='https://example.com/profile'
270+
... )
271+
>>> item = ItemSpec(item_id=1, name='Товар 1', desc='Описание товара 1', price=100.0)
272+
>>> order_line = OrderLineSpec(
273+
... order_id=1,
274+
... order_line_id=1,
275+
... item_line=item,
276+
... quantity=2.0
277+
... )
278+
>>> order = OrderSpec(
279+
... order_id=1,
280+
... user_info=user_profile,
281+
... items_line=[order_line]
282+
... )
283+
>>> print(order)
284+
order_id=1 user_info=ProfileSpec(user_id=1, username='Андрей', surname='Савватеев', second_name='Эдуардович', email='andrey.savvateev@example.com', status='active', bio='Программист и разработчик', url='https://example.com/profile') items_line=[OrderLineSpec(order_id=1, order_line_id=1, item_line=ItemSpec(item_id=1, name='Товар 1', desc='Описание товара 1', price=100.0), quantity=2.0)]
285+
"""
286+
95287
model_config = {
96288
"extra": "forbid"
97289
}
@@ -101,6 +293,42 @@ class OrderSpec(BaseModel):
101293

102294

103295
class OrdersSpec(BaseModel):
296+
"""
297+
Модель списка заказов, которая содержит информацию о заказах пользователей.
298+
299+
Атрибуты:
300+
- market_place_orders (List[OrderSpec]): Список заказов на маркетплейсе.
301+
302+
Конфигурация модели:
303+
- `extra='forbid'`: Запрещает наличие дополнительных полей, не указанных в модели.
304+
305+
Пример использования:
306+
>>> user_profile = ProfileSpec(
307+
... user_id=1,
308+
... username='Андрей',
309+
... surname='Савватеев',
310+
... second_name='Эдуардович',
311+
... email='andrey.savvateev@example.com',
312+
... status='active',
313+
... bio='Программист и разработчик',
314+
... url='https://example.com/profile'
315+
... )
316+
>>> item = ItemSpec(item_id=1, name='Товар 1', desc='Описание товара 1', price=100.0)
317+
>>> order_line = OrderLineSpec(
318+
... order_id=1,
319+
... order_line_id=1,
320+
... item_line=item,
321+
... quantity=2.0
322+
... )
323+
>>> order = OrderSpec(
324+
... order_id=1,
325+
... user_info=user_profile,
326+
... items_line=[order_line]
327+
... )
328+
>>> print(order)
329+
order_id=1 user_info=ProfileSpec(user_id=1, username='Андрей', surname='Савватеев', second_name='Эдуардович', email='andrey.savvateev@example.com', status='active', bio='Программист и разработчик', url='https://example.com/profile') items_line=[OrderLineSpec(order_id=1, order_line_id=1, item_line=ItemSpec(item_id=1, name='Товар 1', desc='Описание товара 1', price=100.0), quantity=2.0)]
330+
"""
331+
104332
model_config = {
105333
"extra": "forbid"
106334
}

tests/Nikulina/__init__.py

Whitespace-only changes.

tests/Savvateev/__init__.py

Whitespace-only changes.

tests/Savvateev/lab_3/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)