Repository: leshchenko1979/fast_bitrix24
Pull request #185 (2022-06-08 18:50:14)

Использовать особое исключение, если сервер вернул ошибку

sourcery-ai[bot]: # Sourcery Code Quality Report

✅ &nbsp;Merging this PR will **increase** code quality in the affected files by **0.47%**.

| Quality metrics | Before | After | Change |
| --- | :---: | :---: | :---: |
| [Complexity](https://docs.sourcery.ai/GitHub/Quality-Report/#complexity) | 1.24 ⭐ | 1.14 ⭐ | -0.10 👍 |
| [Method Length](https://docs.sourcery.ai/GitHub/Quality-Report/#method-length) | 32.71 ⭐ | 31.91 ⭐ | -0.80 👍 |
| [Working memory](https://docs.sourcery.ai/GitHub/Quality-Report/#working-memory) | 5.03 ⭐ | 4.98 ⭐ | -0.05 👍 |
| **[Quality](https://docs.sourcery.ai/GitHub/Quality-Report/#quality-score)** | **87.87%** ⭐| **88.34%** ⭐| **0.47%** 👍 |

| Other metrics | Before | After | Change |
| --- | :---: | :---: | :---: |
| Lines | 447 | 451 | 4 |

| Changed files | Quality Before | Quality After | Quality Change |
| --- | :---: | :---: | :---: |
| fast_bitrix24/bitrix.py | 85.30% ⭐ | 85.21% ⭐ | -0.09% 👎|
| fast_bitrix24/server_response.py | 88.16% ⭐ | 89.52% ⭐ | 1.36% 👍|
| tests/test_server_responses.py | 89.44% ⭐ | 89.44% ⭐ | 0.00% |

Here are some functions in these files that still need a tune-up:

| File | Function | Complexity | Length | Working Memory | Quality | Recommendation |
| --- | --- | :---: | :---: | :---: | :---: | --- |

## Legend and Explanation

The emojis denote the absolute quality of the code:

 - ⭐ excellent
 - 🙂 good
 - 😞 poor
 - ⛔ very poor


The 👍 and 👎 indicate whether the quality has improved or gotten worse with this pull request.

***

Please see our documentation [here](https://docs.sourcery.ai/GitHub/Quality-Report/) for details on how these metrics are calculated.

We are actively working on this report - lots more documentation and extra metrics to come!

Help us [improve](https://research.typeform.com/to/j06Spdfr?type=quality_report&github_login=leshchenko1979&base_repo=https%3A%2F%2Fgithub.com%2Fleshchenko1979%2Ffast_bitrix24.git&base_remote_ref=refs%2Fpull%2F185%2Fhead&base_ref=raise-for-errors&base_sha=e26c4bea3573edcad75104b1f0fbf55bc4ac7be7&head_repo=https%3A%2F%2Fgithub.com%2Fleshchenko1979%2Ffast_bitrix24.git&head_ref=sourcery%2Fraise-for-errors&base_pr_number=185&base_pr_state=open) this quality report!

Patch:
diff --git a/API.md b/API.md
index 6168dc6..3f552ef 100644
--- a/API.md
+++ b/API.md
@@ -171,3 +171,6 @@ leads = b.get_all('crm.lead.list')
 ```
 #### Параметры
 * `max_concurrent_requests: int = 1` - макимальное количество одновременных запросов к серверу (по умолчанию 1).
+
+## Класс `ErrorInServerResponseException(Exception)`
+Это исключение поднимается, когда ответ сервера содержал ошибки.
diff --git a/README.md b/README.md
index 2a489f8..6b25917 100644
--- a/README.md
+++ b/README.md
@@ -128,12 +128,22 @@ b.call('crm.deal.update', tasks)
 ### call(raw=True)
 Вызов `call` с парамтером `raw=True` отправляет на сервер переданные ему параметры в оригинальном, необработанном виде (пропуская упаковку в батчи), и возвращает ответ сервера без какой-либо обработки.
 
-Подобный вызов можно использовать в отладочных целях, но кроме того, придется его использовать для отправки запросов, в параметрах которых есть `None` (None применяется для стирания значения полей, а упаковка в батчи мешает передавать `None`).
+Подобный вызов можно использовать в отладочных целях, но кроме того, придется его использовать для отправки запросов, которые:
+- в параметрах имеют `None` (None применяется для стирания значения полей, а упаковка в батчи мешает передавать `None`),
+- используют устревшие методы Битрикс24, которые принимают на вход список (см. [#157](https://github.com/leshchenko1979/fast_bitrix24/issues/157)).
+
 
 ```python
 # стереть DESCRIPTION в лиде 123
 params = {"ID": 123, "fields": {"DESCRIPTION": None}}
 b.call('crm.lead.update', params, raw=True)
+
+# добавить комментарий к задаче
+b.call(
+    'task.commentitem.add',
+    [123, {"POST_MESSAGE": "Комментарий к задаче"}],
+    raw=True
+)
 ```
 
 ### `call_batch()`
diff --git a/fast_bitrix24/bitrix.py b/fast_bitrix24/bitrix.py
index 0e1448e..27962df 100644
--- a/fast_bitrix24/bitrix.py
+++ b/fast_bitrix24/bitrix.py
@@ -183,9 +183,7 @@ class BitrixAsync:
             await self.srh.run_async(RawCallUserRequest(self, "batch", params).run())
         )
 
-        errors = response.extract_errors()
-        if errors:
-            raise RuntimeError(errors)
+        response.raise_for_errors()
 
         return response.result["result"]
 
diff --git a/fast_bitrix24/server_response.py b/fast_bitrix24/server_response.py
index b8dfb8f..6dfebb8 100644
--- a/fast_bitrix24/server_response.py
+++ b/fast_bitrix24/server_response.py
@@ -4,6 +4,10 @@ from itertools import chain
 from beartype.typing import Dict, List, Union
 
 
+class ErrorInServerResponseException(Exception):
+    pass
+
+
 class ServerResponseParser:
     def __init__(self, response: dict):
         self.response = response
@@ -27,15 +31,6 @@ class ServerResponseParser:
     def result_error(self):
         return self.response.get("result_error")
 
-    def extract_errors(self):
-        if self.is_batch():
-            if self.result.get("result_error"):
-                return self.result["result_error"]
-        elif self.result_error:
-            return self.result_error
-
-        return None
-
     def extract_results(self) -> Union[Dict, List[Dict]]:
         """Вернуть результаты запроса.
 
@@ -45,17 +40,29 @@ class ServerResponseParser:
         Returns:
             Any: Результаты запроса, по возможности превращенные в плоский список.
         """
-        errors = self.extract_errors()
-        if errors:
-            raise RuntimeError(errors)
+        self.raise_for_errors()
 
         if self.is_batch():
             return self.extract_from_batch_response(self.result["result"])
         else:
             return self.extract_from_single_response(self.result)
 
+    def raise_for_errors(self):
+        errors = self.extract_errors()
+        if errors:
+            raise ErrorInServerResponseException(errors)
+
+    def extract_errors(self):
+        if self.is_batch():
+            if self.result.get("result_error"):
+                return self.result["result_error"]
+        elif self.result_error:
+            return self.result_error
+
+        return None
+
     def is_batch(self) -> bool:
-        return "result" in self.response and "result" in self.response["result"]
+        return isinstance(self.result, dict) and "result" in self.result
 
     @staticmethod
     def extract_from_single_response(result: dict):
diff --git a/fast_bitrix24/user_request.py b/fast_bitrix24/user_request.py
index 61cd338..ebb1f62 100644
--- a/fast_bitrix24/user_request.py
+++ b/fast_bitrix24/user_request.py
@@ -260,7 +260,7 @@ class CallUserRequest(GetByIDUserRequest):
         ]
 
 
-class RawCallUserRequest(UserRequestAbstract):
+class RawCallUserRequest():
     """Отправляем на сервер один элемент, не обрабатывая его и не заворачивая в батчи.
 
     Нужно для устревших методов, которые принимают на вход список
@@ -271,22 +271,13 @@ class RawCallUserRequest(UserRequestAbstract):
     """
 
     @beartype
-    def __init__(self, bitrix, method: str, item: Union[Dict, None]):
-        super().__init__(bitrix, method, item)
-
-    def standardized_params(self, p):
-        """Пропускаем все проверки и изменения параметров."""
-        return p
-
-    def standardized_method(self, method: str):
-        """Пропускаем все проверки и изменения методов."""
-        return method
-
-    def check_special_limitations(self):
-        return True
+    def __init__(self, bitrix, method: str, item):
+        self.srh = bitrix.srh
+        self.method = method
+        self.item = item
 
     async def run(self):
-        return await self.srh.single_request(self.method, self.params)
+        return await self.srh.single_request(self.method, self.item)
 
 
 class ListAndGetUserRequest:
diff --git a/setup.py b/setup.py
index 662e729..f7365b4 100644
--- a/setup.py
+++ b/setup.py
@@ -8,7 +8,7 @@ with open('requirements.txt', 'r', encoding='utf-8') as fh:
 
 setuptools.setup(
     name="fast_bitrix24",
-    version="1.5.8",
+    version="1.5.9",
     author="Alexey Leshchenko",
     author_email="leshchenko@gmail.com",
     description='API wrapper для быстрого получения данных от Битрикс24 через '


Test patch:
diff --git a/tests/real_responses/single_add.py b/tests/real_responses/single_add.py
new file mode 100644
index 0000000..1a65766
--- /dev/null
+++ b/tests/real_responses/single_add.py
@@ -0,0 +1,138 @@
+response = {
+    "result": {
+        "task": {
+            "id": "456",
+            "title": "Алм",
+            "description": "Курс на социально-ориентированный национальный проект станет частью наших традиций",
+            "descriptionInBbcode": "Y",
+            "declineReason": None,
+            "priority": "1",
+            "status": "2",
+            "notViewed": "N",
+            "statusComplete": "1",
+            "multitask": "N",
+            "stageId": "0",
+            "responsibleId": "1",
+            "responsibleName": "Korvin",
+            "responsibleLastName": "-Control",
+            "responsibleSecondName": None,
+            "responsibleLogin": "admin",
+            "responsibleWorkPosition": None,
+            "responsiblePhoto": None,
+            "dateStart": None,
+            "durationFact": None,
+            "timeEstimate": "0",
+            "timeSpentInLogs": None,
+            "replicate": "N",
+            "deadline": "2022-05-09T01:00:00+04:00",
+            "deadlineOrig": "2022-05-09 00:00:00",
+            "startDatePlan": None,
+            "endDatePlan": None,
+            "createdBy": "1",
+            "createdByName": "Korvin",
+            "createdByLastName": "-Control",
+            "createdBySecondName": None,
+            "createdByLogin": "admin",
+            "createdByWorkPosition": None,
+            "createdByPhoto": None,
+            "createdDate": "2022-06-08T21:54:00+04:00",
+            "changedBy": "1",
+            "changedDate": "2022-06-08T21:54:00+04:00",
+            "statusChangedBy": "1",
+            "statusChangedDate": "2022-06-08T21:54:00+04:00",
+            "closedBy": None,
+            "closedDate": None,
+            "activityDate": "2022-06-08T21:54:00+04:00",
+            "guid": "{bbe24f38-df7f-42d4-b172-9ebf432559ed}",
+            "xmlId": None,
+            "mark": None,
+            "allowChangeDeadline": "N",
+            "allowTimeTracking": "N",
+            "matchWorkTime": "N",
+            "taskControl": "N",
+            "addInReport": "N",
+            "groupId": "0",
+            "forumTopicId": None,
+            "parentId": None,
+            "commentsCount": None,
+            "serviceCommentsCount": None,
+            "forumId": None,
+            "siteId": "s1",
+            "subordinate": "N",
+            "exchangeModified": None,
+            "exchangeId": None,
+            "outlookVersion": "1",
+            "viewedDate": None,
+            "deadlineCounted": "0",
+            "forkedByTemplateId": None,
+            "favorite": "N",
+            "sorting": None,
+            "durationPlanSeconds": None,
+            "durationTypeAll": "days",
+            "durationPlan": None,
+            "durationType": "days",
+            "isMuted": "N",
+            "isPinned": "N",
+            "isPinnedInGroup": "N",
+            "ufCrmTask": False,
+            "ufTaskWebdavFiles": False,
+            "ufMailMessage": None,
+            "auditors": [],
+            "accomplices": [],
+            "tags": ["скпэ22-2"],
+            "checklist": [],
+            "files": [],
+            "dependsOn": [],
+            "group": [],
+            "creator": {
+                "id": "1",
+                "name": "Korvin -Control",
+                "link": "/company/personal/user/1/",
+                "icon": "/bitrix/images/tasks/default_avatar.png",
+            },
+            "responsible": {
+                "id": "1",
+                "name": "Korvin -Control",
+                "link": "/company/personal/user/1/",
+                "icon": "/bitrix/images/tasks/default_avatar.png",
+            },
+            "newCommentsCount": 0,
+            "action": {
+                "accept": False,
+                "decline": False,
+                "complete": True,
+                "approve": False,
+                "disapprove": False,
+                "start": True,
+                "pause": False,
+                "delegate": True,
+                "remove": True,
+                "edit": True,
+                "defer": True,
+                "renew": False,
+                "create": True,
+                "changeDeadline": True,
+                "checklistAddItems": True,
+                "addFavorite": True,
+                "deleteFavorite": False,
+                "rate": True,
+                "edit.originator": False,
+                "checklist.reorder": True,
+                "elapsedtime.add": True,
+                "dayplan.timer.toggle": False,
+                "edit.plan": True,
+                "checklist.add": True,
+                "favorite.add": True,
+                "favorite.delete": False,
+            },
+        }
+    },
+    "time": {
+        "start": 1654710840.774196,
+        "finish": 1654710840.852366,
+        "duration": 0.0781700611114502,
+        "processing": 0.06392693519592285,
+        "date_start": "2022-06-08T20:54:00+03:00",
+        "date_finish": "2022-06-08T20:54:00+03:00",
+    },
+}
diff --git a/tests/test_server_responses.py b/tests/test_server_responses.py
index 5d29d5a..4e1171f 100644
--- a/tests/test_server_responses.py
+++ b/tests/test_server_responses.py
@@ -3,7 +3,7 @@ import logging
 import pytest
 from beartype.typing import Dict, List, Union
 from fast_bitrix24.logger import logger
-from fast_bitrix24.server_response import ServerResponseParser
+from fast_bitrix24.server_response import ErrorInServerResponseException, ServerResponseParser
 from fast_bitrix24.srh import ServerRequestHandler
 
 logger.addHandler(logging.StreamHandler())
@@ -48,7 +48,7 @@ def test_batch_single_page_error(bx_dummy):
     from tests.real_responses.crm_get_batch_mix_success_and_errors import response
 
     bx_dummy.srh = MockSRH(response)
-    with pytest.raises(RuntimeError):
+    with pytest.raises(ErrorInServerResponseException):
         bx_dummy.get_by_ID("crm.lead.get", [0, 1, 35943])
 
 
@@ -140,3 +140,22 @@ def test_stagehistory(bx_dummy, recwarn):
     print(w.message for w in recwarn.list)
     with pytest.raises(Exception):
         recwarn.pop(RuntimeWarning)
+
+
+def test_single_add(bx_dummy):
+    from tests.real_responses.single_add import response
+
+    bx_dummy.srh = MockSRH(response)
+    results = bx_dummy.call(
+        "tasks.task.add",
+        {
+            "fields": {
+                "TITLE": "АГ",
+                "DESCRIPTION": "Сложно сказать, почему потускнели светлые лики икон",
+                "RESPONSIBLE_ID": 1,
+                "TAGS": "скпэ22-4",
+                "DEADLINE": "2022/07/30",
+            }
+        },
+    )
+    assert isinstance(results, dict)
diff --git a/tests/test_sync.py b/tests/test_sync.py
index 7fb2c1b..091831a 100644
--- a/tests/test_sync.py
+++ b/tests/test_sync.py
@@ -2,6 +2,7 @@ import os
 
 import pytest
 
+
 @pytest.mark.skipif(
     not os.getenv("FAST_BITRIX24_TEST_WEBHOOK"),
     reason="Нет аккаунта, на котором можно проверить",
@@ -137,7 +138,9 @@ class TestsWithLiveServer:
         def test_case(self, get_test):
             b = get_test
 
-            with pytest.raises(RuntimeError, match="Could not find value for parameter"):
+            with pytest.raises(
+                RuntimeError, match="Could not find value for parameter"
+            ):
                 b.call("disk.file.get", [{"ID": 1}])
 
             with pytest.raises(RuntimeError, match="Could not find entity with id"):
@@ -150,7 +153,6 @@ class TestsWithLiveServer:
             )
             b.get_by_ID("crm.lead.delete", [lead_no])
 
-
     class TestParamsEncoding:
         def test_mobile_phone(self, get_test):
             b = get_test
@@ -204,6 +206,7 @@ class TestsWithLiveServer:
 
             assert len(product_rows) == len(result_rows)
 
+
 class TestErrors:
     def test_get_all(self, bx_dummy):
         b = bx_dummy
@@ -242,3 +245,5 @@ class TestErrors:
 
         with pytest.raises(Exception):
             b.call("_", {})
+
+        b.call("_", [1, {"a": 2}], raw=True)