asyncly 0.4.0__tar.gz → 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. {asyncly-0.4.0 → asyncly-0.5.0}/PKG-INFO +21 -10
  2. {asyncly-0.4.0 → asyncly-0.5.0}/README.rst +15 -9
  3. {asyncly-0.4.0 → asyncly-0.5.0}/asyncly/client/base.py +2 -9
  4. {asyncly-0.4.0 → asyncly-0.5.0}/asyncly/client/handlers/base.py +2 -4
  5. asyncly-0.5.0/asyncly/client/metrics/instrumentable_client.py +148 -0
  6. asyncly-0.5.0/asyncly/client/metrics/route_resolver.py +13 -0
  7. asyncly-0.5.0/asyncly/client/metrics/sinks/base.py +14 -0
  8. asyncly-0.5.0/asyncly/client/metrics/sinks/noop.py +14 -0
  9. asyncly-0.5.0/asyncly/client/metrics/sinks/opentelemetry.py +52 -0
  10. asyncly-0.5.0/asyncly/client/metrics/sinks/prometheus.py +64 -0
  11. {asyncly-0.4.0 → asyncly-0.5.0}/asyncly/client/timeout.py +1 -1
  12. asyncly-0.5.0/asyncly/client/typing.py +21 -0
  13. asyncly-0.5.0/asyncly/srvmocker/responses/__init__.py +0 -0
  14. asyncly-0.5.0/asyncly/srvmocker/serialization/__init__.py +0 -0
  15. {asyncly-0.4.0 → asyncly-0.5.0}/pyproject.toml +24 -1
  16. {asyncly-0.4.0 → asyncly-0.5.0}/.gitignore +0 -0
  17. {asyncly-0.4.0 → asyncly-0.5.0}/asyncly/__init__.py +0 -0
  18. {asyncly-0.4.0 → asyncly-0.5.0}/asyncly/client/__init__.py +0 -0
  19. {asyncly-0.4.0 → asyncly-0.5.0}/asyncly/client/handlers/__init__.py +0 -0
  20. {asyncly-0.4.0 → asyncly-0.5.0}/asyncly/client/handlers/exceptions.py +0 -0
  21. {asyncly-0.4.0 → asyncly-0.5.0}/asyncly/client/handlers/json.py +0 -0
  22. {asyncly-0.4.0 → asyncly-0.5.0}/asyncly/client/handlers/msgspec.py +0 -0
  23. {asyncly-0.4.0 → asyncly-0.5.0}/asyncly/client/handlers/pydantic.py +0 -0
  24. {asyncly-0.4.0/asyncly/srvmocker/responses → asyncly-0.5.0/asyncly/client/metrics}/__init__.py +0 -0
  25. {asyncly-0.4.0/asyncly/srvmocker/serialization → asyncly-0.5.0/asyncly/client/metrics/sinks}/__init__.py +0 -0
  26. {asyncly-0.4.0 → asyncly-0.5.0}/asyncly/py.typed +0 -0
  27. {asyncly-0.4.0 → asyncly-0.5.0}/asyncly/srvmocker/__init__.py +0 -0
  28. {asyncly-0.4.0 → asyncly-0.5.0}/asyncly/srvmocker/constants.py +0 -0
  29. {asyncly-0.4.0 → asyncly-0.5.0}/asyncly/srvmocker/handlers.py +0 -0
  30. {asyncly-0.4.0 → asyncly-0.5.0}/asyncly/srvmocker/models.py +0 -0
  31. {asyncly-0.4.0 → asyncly-0.5.0}/asyncly/srvmocker/responses/base.py +0 -0
  32. {asyncly-0.4.0 → asyncly-0.5.0}/asyncly/srvmocker/responses/content.py +0 -0
  33. {asyncly-0.4.0 → asyncly-0.5.0}/asyncly/srvmocker/responses/json.py +0 -0
  34. {asyncly-0.4.0 → asyncly-0.5.0}/asyncly/srvmocker/responses/msgpack.py +0 -0
  35. {asyncly-0.4.0 → asyncly-0.5.0}/asyncly/srvmocker/responses/sequence.py +0 -0
  36. {asyncly-0.4.0 → asyncly-0.5.0}/asyncly/srvmocker/responses/timeout.py +0 -0
  37. {asyncly-0.4.0 → asyncly-0.5.0}/asyncly/srvmocker/responses/toml.py +0 -0
  38. {asyncly-0.4.0 → asyncly-0.5.0}/asyncly/srvmocker/responses/yaml.py +0 -0
  39. {asyncly-0.4.0 → asyncly-0.5.0}/asyncly/srvmocker/serialization/base.py +0 -0
  40. {asyncly-0.4.0 → asyncly-0.5.0}/asyncly/srvmocker/serialization/json.py +0 -0
  41. {asyncly-0.4.0 → asyncly-0.5.0}/asyncly/srvmocker/serialization/msgpack.py +0 -0
  42. {asyncly-0.4.0 → asyncly-0.5.0}/asyncly/srvmocker/serialization/toml.py +0 -0
  43. {asyncly-0.4.0 → asyncly-0.5.0}/asyncly/srvmocker/serialization/yaml.py +0 -0
  44. {asyncly-0.4.0 → asyncly-0.5.0}/asyncly/srvmocker/service.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: asyncly
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Simple HTTP client and server for your integrations based on aiohttp
5
5
  Project-URL: Homepage, https://github.com/andy-takker/asyncly
6
6
  Project-URL: Source, https://github.com/andy-takker/asyncly
@@ -22,6 +22,7 @@ Classifier: Programming Language :: Python :: 3
22
22
  Classifier: Programming Language :: Python :: 3.10
23
23
  Classifier: Programming Language :: Python :: 3.11
24
24
  Classifier: Programming Language :: Python :: 3.12
25
+ Classifier: Programming Language :: Python :: 3.13
25
26
  Classifier: Topic :: Internet
26
27
  Classifier: Topic :: Internet :: WWW/HTTP
27
28
  Classifier: Topic :: Software Development
@@ -31,8 +32,12 @@ Requires-Python: <4,>=3.10
31
32
  Requires-Dist: aiohttp<4,>=3.9.5
32
33
  Provides-Extra: msgspec
33
34
  Requires-Dist: msgspec<0.20,>=0.19.0; extra == 'msgspec'
35
+ Provides-Extra: opentelemetry
36
+ Requires-Dist: opentelemetry-sdk>=1.37.0; extra == 'opentelemetry'
34
37
  Provides-Extra: orjson
35
38
  Requires-Dist: orjson<4,>=3.10.6; extra == 'orjson'
39
+ Provides-Extra: prometheus
40
+ Requires-Dist: prometheus-client>=0.22.1; extra == 'prometheus'
36
41
  Provides-Extra: pydantic
37
42
  Requires-Dist: pydantic<3,>=2.8.2; extra == 'pydantic'
38
43
  Description-Content-Type: text/x-rst
@@ -84,15 +89,19 @@ For example, with msgspec_:
84
89
 
85
90
  Complete table of extras below:
86
91
 
87
- +-------------------------------------+----------------------------------+
88
- | example | description |
89
- +=====================================+==================================+
90
- | ``pip install "asyncly[msgspec]"`` | For using msgspec_ structs |
91
- +-------------------------------------+----------------------------------+
92
- | ``pip install "asyncly[orjson]"`` | For fast parsing json by orjson_ |
93
- +-------------------------------------+----------------------------------+
94
- | ``pip install "asyncly[pydantic]"`` | For using pydantic_ models |
95
- +-------------------------------------+----------------------------------+
92
+ +------------------------------------------+-----------------------------------+
93
+ | example | description |
94
+ +==========================================+===================================+
95
+ | ``pip install "asyncly[msgspec]"`` | For using msgspec_ structs |
96
+ +------------------------------------------+-----------------------------------+
97
+ | ``pip install "asyncly[orjson]"`` | For fast parsing json by orjson_ |
98
+ +------------------------------------------+-----------------------------------+
99
+ | ``pip install "asyncly[pydantic]"`` | For using pydantic_ models |
100
+ +------------------------------------------+-----------------------------------+
101
+ | ``pip install "asyncly[prometheus]"`` | To collect Prometheus_ metrics |
102
+ +------------------------------------------+-----------------------------------+
103
+ | ``pip install "asyncly[opentelemetry]"`` | To collect OpenTelemetry_ metrics |
104
+ +------------------------------------------+-----------------------------------+
96
105
 
97
106
  Quick start guide
98
107
  -----------------
@@ -218,6 +227,8 @@ Useful responses and serializers
218
227
  .. _msgspec: https://github.com/jcrist/msgspec
219
228
  .. _orjson: https://github.com/ijl/orjson
220
229
  .. _pydantic: https://github.com/pydantic/pydantic
230
+ .. _Prometheus: https://prometheus.io
231
+ .. _OpenTelemetry: https://opentelemetry.io
221
232
 
222
233
  .. _examples/catfact_client.py: https://github.com/andy-takker/asyncly/blob/master/examples/catfact_client.py
223
234
  .. _examples/test_catfact_client.py: https://github.com/andy-takker/asyncly/blob/master/examples/test_catfact_client.py
@@ -45,15 +45,19 @@ For example, with msgspec_:
45
45
 
46
46
  Complete table of extras below:
47
47
 
48
- +-------------------------------------+----------------------------------+
49
- | example | description |
50
- +=====================================+==================================+
51
- | ``pip install "asyncly[msgspec]"`` | For using msgspec_ structs |
52
- +-------------------------------------+----------------------------------+
53
- | ``pip install "asyncly[orjson]"`` | For fast parsing json by orjson_ |
54
- +-------------------------------------+----------------------------------+
55
- | ``pip install "asyncly[pydantic]"`` | For using pydantic_ models |
56
- +-------------------------------------+----------------------------------+
48
+ +------------------------------------------+-----------------------------------+
49
+ | example | description |
50
+ +==========================================+===================================+
51
+ | ``pip install "asyncly[msgspec]"`` | For using msgspec_ structs |
52
+ +------------------------------------------+-----------------------------------+
53
+ | ``pip install "asyncly[orjson]"`` | For fast parsing json by orjson_ |
54
+ +------------------------------------------+-----------------------------------+
55
+ | ``pip install "asyncly[pydantic]"`` | For using pydantic_ models |
56
+ +------------------------------------------+-----------------------------------+
57
+ | ``pip install "asyncly[prometheus]"`` | To collect Prometheus_ metrics |
58
+ +------------------------------------------+-----------------------------------+
59
+ | ``pip install "asyncly[opentelemetry]"`` | To collect OpenTelemetry_ metrics |
60
+ +------------------------------------------+-----------------------------------+
57
61
 
58
62
  Quick start guide
59
63
  -----------------
@@ -179,6 +183,8 @@ Useful responses and serializers
179
183
  .. _msgspec: https://github.com/jcrist/msgspec
180
184
  .. _orjson: https://github.com/ijl/orjson
181
185
  .. _pydantic: https://github.com/pydantic/pydantic
186
+ .. _Prometheus: https://prometheus.io
187
+ .. _OpenTelemetry: https://opentelemetry.io
182
188
 
183
189
  .. _examples/catfact_client.py: https://github.com/andy-takker/asyncly/blob/master/examples/catfact_client.py
184
190
  .. _examples/test_catfact_client.py: https://github.com/andy-takker/asyncly/blob/master/examples/test_catfact_client.py
@@ -1,5 +1,4 @@
1
- import sys
2
- from typing import Any, Literal
1
+ from typing import Any
3
2
 
4
3
  from aiohttp import ClientSession
5
4
  from aiohttp.client import DEFAULT_TIMEOUT
@@ -10,13 +9,7 @@ from asyncly.client.handlers.base import (
10
9
  apply_handler,
11
10
  )
12
11
  from asyncly.client.timeout import TimeoutType, get_timeout
13
-
14
- if sys.version_info >= (3, 11):
15
- from http import HTTPMethod
16
-
17
- MethodType = HTTPMethod | Literal["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD"]
18
- else:
19
- MethodType = Literal["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD"]
12
+ from asyncly.client.typing import MethodType
20
13
 
21
14
 
22
15
  class BaseHttpClient:
@@ -1,12 +1,10 @@
1
- from collections.abc import Callable, Mapping
2
- from http import HTTPStatus
1
+ from collections.abc import Callable
3
2
  from typing import Any
4
3
 
5
4
  from aiohttp import ClientResponse
6
5
 
7
6
  from asyncly.client.handlers.exceptions import UnhandledStatusException
8
-
9
- ResponseHandlersType = Mapping[HTTPStatus | int | str, Callable]
7
+ from asyncly.client.typing import ResponseHandlersType
10
8
 
11
9
 
12
10
  async def apply_handler(
@@ -0,0 +1,148 @@
1
+ from http import HTTPStatus
2
+ from time import perf_counter
3
+ from types import TracebackType
4
+ from typing import Any
5
+
6
+ from aiohttp import ClientResponse, ClientSession
7
+ from aiohttp.client import DEFAULT_TIMEOUT
8
+ from yarl import URL
9
+
10
+ from asyncly.client.base import BaseHttpClient, MethodType
11
+ from asyncly.client.metrics.route_resolver import default_route_resolver
12
+ from asyncly.client.metrics.sinks.base import MetricsSink
13
+ from asyncly.client.metrics.sinks.noop import NoopSink
14
+ from asyncly.client.timeout import TimeoutType
15
+ from asyncly.client.typing import ResponseHandler, ResponseHandlersType, RouteResolver
16
+
17
+
18
+ class InstrumentableHttpClient(BaseHttpClient):
19
+ __slots__ = ("_metrics_sink", "_resolve_route") + BaseHttpClient.__slots__
20
+
21
+ def __init__(
22
+ self,
23
+ url: URL | str,
24
+ session: ClientSession,
25
+ client_name: str,
26
+ ) -> None:
27
+ super().__init__(url=url, session=session, client_name=client_name)
28
+ self._metrics_sink: MetricsSink = NoopSink()
29
+ self._resolve_route: RouteResolver = default_route_resolver
30
+
31
+ def enable_metrics(
32
+ self, sink: MetricsSink, *, route_resolver: RouteResolver | None = None
33
+ ) -> None:
34
+ self._metrics_sink = sink
35
+ if route_resolver is not None:
36
+ self._resolve_route = route_resolver
37
+
38
+ def disable_metrics(self) -> None:
39
+ self._metrics_sink = NoopSink()
40
+ self._resolve_route = default_route_resolver
41
+
42
+ def instrument( # type: ignore[no-untyped-def]
43
+ self, sink: MetricsSink, *, route_resolver: RouteResolver | None = None
44
+ ):
45
+ client = self
46
+
47
+ class _Ctx:
48
+ def __enter__(self) -> "InstrumentableHttpClient":
49
+ self._prev_sink = client._metrics_sink
50
+ self._prev_resolver = client._resolve_route
51
+ client.enable_metrics(sink, route_resolver=route_resolver)
52
+ return client
53
+
54
+ def __exit__(
55
+ self,
56
+ exc_type: type[BaseException] | None,
57
+ exc: BaseException | None,
58
+ tb: TracebackType | None,
59
+ ) -> None:
60
+ client._metrics_sink = self._prev_sink
61
+ client._resolve_route = self._prev_resolver
62
+
63
+ return _Ctx()
64
+
65
+ async def _make_req(
66
+ self,
67
+ /,
68
+ method: MethodType,
69
+ url: URL,
70
+ handlers: ResponseHandlersType,
71
+ timeout: TimeoutType = DEFAULT_TIMEOUT,
72
+ **kwargs: Any,
73
+ ) -> Any:
74
+ # Быстрый путь: метрики Noop → почти нулевая накладная
75
+ sink = self._metrics_sink
76
+ if isinstance(sink, NoopSink):
77
+ return await super()._make_req(
78
+ method=method,
79
+ url=url,
80
+ handlers=handlers,
81
+ timeout=timeout,
82
+ **kwargs,
83
+ )
84
+
85
+ route_label = self._resolve_route(url)
86
+ start = perf_counter()
87
+ chosen_status: dict[str, int | HTTPStatus | str | None] = {"value": None}
88
+
89
+ # Заворачиваем хэндлеры, чтобы знать какой статус сработал
90
+ wrapped_handlers = _wrap_handlers_with_status_mark(handlers, chosen_status)
91
+
92
+ error_type: str | None = None
93
+ status_for_metrics: int | str = "unknown"
94
+ try:
95
+ result = await super()._make_req(
96
+ method=method, url=url, handlers=wrapped_handlers, timeout=timeout
97
+ )
98
+ v = chosen_status["value"]
99
+ if isinstance(v, HTTPStatus):
100
+ status_for_metrics = int(v)
101
+ elif isinstance(v, int):
102
+ status_for_metrics = v
103
+ else:
104
+ status_for_metrics = "ok"
105
+ return result
106
+ except Exception as e:
107
+ status = (
108
+ chosen_status["value"]
109
+ or getattr(e, "status", None)
110
+ or getattr(e, "status_code", None)
111
+ )
112
+ status_for_metrics = int(status) if isinstance(status, int) else "exception"
113
+ error_type = type(e).__name__
114
+ raise
115
+ finally:
116
+ duration = perf_counter() - start
117
+ sink.observe_request(
118
+ client=self._client_name,
119
+ method=method,
120
+ route=route_label,
121
+ status=status_for_metrics,
122
+ duration_seconds=duration,
123
+ error_type=error_type,
124
+ )
125
+
126
+
127
+ def _wrap_handlers_with_status_mark(
128
+ handlers: ResponseHandlersType,
129
+ chosen_status: dict[str, int | HTTPStatus | str | None],
130
+ ) -> ResponseHandlersType:
131
+ try:
132
+ wrapped: dict[int | HTTPStatus | str, ResponseHandler] = {}
133
+ for k, handler in handlers.items():
134
+ wrapped[k] = _wrap_one(handler, chosen_status)
135
+ return wrapped
136
+ except AttributeError:
137
+ return handlers
138
+
139
+
140
+ def _wrap_one(
141
+ handler: ResponseHandler,
142
+ chosen_status: dict[str, int | HTTPStatus | str | None],
143
+ ) -> ResponseHandler:
144
+ async def _wrapped(response: ClientResponse) -> Any:
145
+ chosen_status["value"] = response.status
146
+ return await handler(response)
147
+
148
+ return _wrapped
@@ -0,0 +1,13 @@
1
+ from yarl import URL
2
+
3
+
4
+ def default_route_resolver(url: URL) -> str:
5
+ parts: list[str] = []
6
+ for p in url.path.split("/"):
7
+ if not p:
8
+ continue
9
+ if p.isdigit() or (len(p) in (8, 16, 32, 36) and any(ch.isalpha() for ch in p)):
10
+ parts.append(":id")
11
+ else:
12
+ parts.append(p)
13
+ return "/" + "/".join(parts) if parts else "/"
@@ -0,0 +1,14 @@
1
+ from typing import Protocol
2
+
3
+
4
+ class MetricsSink(Protocol):
5
+ def observe_request(
6
+ self,
7
+ *,
8
+ client: str,
9
+ method: str,
10
+ route: str,
11
+ status: int | str,
12
+ duration_seconds: float,
13
+ error_type: str | None = None,
14
+ ) -> None: ...
@@ -0,0 +1,14 @@
1
+ class NoopSink:
2
+ """Синк по умолчанию: ничего не делает."""
3
+
4
+ def observe_request(
5
+ self,
6
+ *,
7
+ client: str,
8
+ method: str,
9
+ route: str,
10
+ status: int | str,
11
+ duration_seconds: float,
12
+ error_type: str | None = None,
13
+ ) -> None:
14
+ return
@@ -0,0 +1,52 @@
1
+ from opentelemetry.metrics import Meter
2
+
3
+
4
+ class OpenTelemetrySink:
5
+ def __init__(self, meter: Meter) -> None:
6
+ # Counter: количество
7
+ self._req_counter = meter.create_counter(
8
+ name="http_client_requests_total",
9
+ unit="1",
10
+ description="Total HTTP client requests",
11
+ )
12
+ # Histogram: длительность
13
+ self._req_hist = meter.create_histogram(
14
+ name="http_client_request_seconds",
15
+ unit="s",
16
+ description="HTTP client request duration including handler",
17
+ )
18
+ # Counter: ошибки
19
+ self._err_counter = meter.create_counter(
20
+ name="http_client_errors_total",
21
+ unit="1",
22
+ description="Total HTTP client errors",
23
+ )
24
+
25
+ def observe_request(
26
+ self,
27
+ *,
28
+ client: str,
29
+ method: str,
30
+ route: str,
31
+ status: int | str,
32
+ duration_seconds: float,
33
+ error_type: str | None = None,
34
+ ) -> None:
35
+ attrs = {
36
+ "client": client,
37
+ "method": method,
38
+ "route": route,
39
+ "status": str(status),
40
+ }
41
+ self._req_counter.add(1, attributes=attrs)
42
+ self._req_hist.record(duration_seconds, attributes=attrs)
43
+ if error_type:
44
+ self._err_counter.add(
45
+ 1,
46
+ attributes={
47
+ "client": client,
48
+ "method": method,
49
+ "route": route,
50
+ "error_type": error_type,
51
+ },
52
+ )
@@ -0,0 +1,64 @@
1
+ from collections.abc import Iterable
2
+
3
+ from prometheus_client import Counter, Histogram
4
+ from prometheus_client.registry import REGISTRY, CollectorRegistry
5
+
6
+
7
+ class PrometheusSink:
8
+ def __init__(
9
+ self,
10
+ namespace: str = "asyncly",
11
+ subsystem: str = "client",
12
+ buckets: Iterable[float] = (
13
+ 0.005,
14
+ 0.01,
15
+ 0.025,
16
+ 0.05,
17
+ 0.1,
18
+ 0.25,
19
+ 0.5,
20
+ 1.0,
21
+ 2.5,
22
+ 5.0,
23
+ 10.0,
24
+ ),
25
+ registry: CollectorRegistry = REGISTRY,
26
+ ) -> None:
27
+ metric_prefix = f"{namespace}_{subsystem}"
28
+ self._latency = Histogram(
29
+ f"{metric_prefix}_request_seconds",
30
+ "HTTP client request duration including handler",
31
+ ("client", "method", "route", "status"),
32
+ buckets=tuple(buckets),
33
+ registry=registry,
34
+ )
35
+ self._total = Counter(
36
+ f"{metric_prefix}_requests_total",
37
+ "Total HTTP client requests",
38
+ ("client", "method", "route", "status"),
39
+ registry=registry,
40
+ )
41
+ self._errors = Counter(
42
+ f"{metric_prefix}_errors_total",
43
+ "Total HTTP client errors",
44
+ ("client", "method", "route", "error_type"),
45
+ registry=registry,
46
+ )
47
+
48
+ def observe_request(
49
+ self,
50
+ *,
51
+ client: str,
52
+ method: str,
53
+ route: str,
54
+ status: int | str,
55
+ duration_seconds: float,
56
+ error_type: str | None = None,
57
+ ) -> None:
58
+ status_label = str(status)
59
+ self._total.labels(client, method, route, status_label).inc()
60
+ self._latency.labels(client, method, route, status_label).observe(
61
+ duration_seconds
62
+ )
63
+ if error_type:
64
+ self._errors.labels(client, method, route, error_type).inc()
@@ -3,7 +3,7 @@ from functools import singledispatch
3
3
 
4
4
  from aiohttp import ClientTimeout
5
5
 
6
- TimeoutType = ClientTimeout | timedelta | int | float
6
+ from asyncly.client.typing import TimeoutType
7
7
 
8
8
 
9
9
  @singledispatch
@@ -0,0 +1,21 @@
1
+ import sys
2
+ from collections.abc import Callable, Mapping
3
+ from datetime import timedelta
4
+ from http import HTTPStatus
5
+ from typing import Literal
6
+
7
+ from aiohttp import ClientTimeout
8
+ from yarl import URL
9
+
10
+ ResponseHandler = Callable
11
+ ResponseHandlersType = Mapping[HTTPStatus | int | str, ResponseHandler]
12
+
13
+ TimeoutType = ClientTimeout | timedelta | int | float
14
+ RouteResolver = Callable[[URL], str]
15
+
16
+ if sys.version_info >= (3, 11):
17
+ from http import HTTPMethod
18
+
19
+ MethodType = HTTPMethod | Literal["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD"]
20
+ else:
21
+ MethodType = Literal["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD"]
File without changes
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "asyncly"
3
- version = "0.4.0"
3
+ version = "0.5.0"
4
4
  description = "Simple HTTP client and server for your integrations based on aiohttp"
5
5
  authors = [{ name = "Sergey Natalenko", email = "sergey.natalenko@mail.ru" }]
6
6
  requires-python = ">=3.10, <4"
@@ -26,6 +26,7 @@ classifiers = [
26
26
  "Programming Language :: Python :: 3.10",
27
27
  "Programming Language :: Python :: 3.11",
28
28
  "Programming Language :: Python :: 3.12",
29
+ "Programming Language :: Python :: 3.13",
29
30
  "Topic :: Internet",
30
31
  "Topic :: Internet :: WWW/HTTP",
31
32
  "Topic :: Software Development :: Libraries :: Python Modules",
@@ -38,6 +39,12 @@ dependencies = ["aiohttp>=3.9.5,<4"]
38
39
  msgspec = ["msgspec>=0.19.0,<0.20"]
39
40
  pydantic = ["pydantic>=2.8.2,<3"]
40
41
  orjson = ["orjson>=3.10.6,<4"]
42
+ prometheus = [
43
+ "prometheus-client>=0.22.1",
44
+ ]
45
+ opentelemetry = [
46
+ "opentelemetry-sdk>=1.37.0",
47
+ ]
41
48
 
42
49
  [project.urls]
43
50
  Homepage = "https://github.com/andy-takker/asyncly"
@@ -82,7 +89,23 @@ source = ["asyncly"]
82
89
  command_line = "-m pytest"
83
90
 
84
91
  [tool.coverage.report]
92
+ fail_under = 90
85
93
  show_missing = true
94
+ skip_covered = false
95
+ skip_empty = true
96
+ exclude_also = [
97
+ "def __repr__",
98
+ "if self.debug:",
99
+ "if settings.DEBUG",
100
+ "raise AssertionError",
101
+ "raise NotImplementedError",
102
+ "if 0:",
103
+ "if __name__ == __main__:",
104
+ "if TYPE_CHECKING:",
105
+ "class .*\\bProtocol\\):",
106
+ "@(abc\\.)?abstractmethod",
107
+ "pass",
108
+ ]
86
109
 
87
110
  [tool.coverage.xml]
88
111
  output = "coverage.xml"
File without changes
File without changes
File without changes