flipt 1.3.0__py3-none-any.whl

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.
flipt/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ from .async_client import AsyncFliptClient
2
+ from .sync_client import FliptClient
3
+
4
+ __all__ = [
5
+ "FliptClient",
6
+ "AsyncFliptClient",
7
+ ]
flipt/async_client.py ADDED
@@ -0,0 +1,21 @@
1
+ import httpx
2
+
3
+ from .authentication import AuthenticationStrategy
4
+ from .evaluation import AsyncEvaluation
5
+ from .flags import AsyncFlag
6
+
7
+
8
+ class AsyncFliptClient:
9
+ def __init__(
10
+ self,
11
+ url: str = "http://localhost:8080",
12
+ timeout: int = 60,
13
+ authentication: AuthenticationStrategy | None = None,
14
+ ):
15
+ self.httpx_client = httpx.AsyncClient(timeout=timeout)
16
+
17
+ self.evaluation = AsyncEvaluation(url, authentication, self.httpx_client)
18
+ self.flag = AsyncFlag(url, authentication, self.httpx_client)
19
+
20
+ async def close(self) -> None:
21
+ await self.httpx_client.aclose()
@@ -0,0 +1,19 @@
1
+ class AuthenticationStrategy:
2
+ def authenticate(self, headers: dict[str, str]) -> None:
3
+ raise NotImplementedError
4
+
5
+
6
+ class ClientTokenAuthentication(AuthenticationStrategy):
7
+ def __init__(self, token: str) -> None:
8
+ self.token = token
9
+
10
+ def authenticate(self, headers: dict[str, str]) -> None:
11
+ headers["Authorization"] = f"Bearer {self.token}"
12
+
13
+
14
+ class JWTAuthentication(AuthenticationStrategy):
15
+ def __init__(self, token: str) -> None:
16
+ self.token = token
17
+
18
+ def authenticate(self, headers: dict[str, str]) -> None:
19
+ headers["Authorization"] = f"JWT {self.token}"
@@ -0,0 +1,29 @@
1
+ from .async_evaluation_client import AsyncEvaluation
2
+ from .models import (
3
+ BatchEvaluationRequest,
4
+ BatchEvaluationResponse,
5
+ BooleanEvaluationResponse,
6
+ ErrorEvaluationReason,
7
+ ErrorEvaluationResponse,
8
+ EvaluationReason,
9
+ EvaluationRequest,
10
+ EvaluationResponse,
11
+ EvaluationResponseType,
12
+ VariantEvaluationResponse,
13
+ )
14
+ from .sync_evaluation_client import Evaluation
15
+
16
+ __all__ = [
17
+ "Evaluation",
18
+ "AsyncEvaluation",
19
+ "EvaluationResponseType",
20
+ "EvaluationReason",
21
+ "ErrorEvaluationReason",
22
+ "EvaluationRequest",
23
+ "BatchEvaluationRequest",
24
+ "VariantEvaluationResponse",
25
+ "BooleanEvaluationResponse",
26
+ "ErrorEvaluationResponse",
27
+ "EvaluationResponse",
28
+ "BatchEvaluationResponse",
29
+ ]
@@ -0,0 +1,69 @@
1
+ from http import HTTPStatus
2
+
3
+ import httpx
4
+
5
+ from flipt.authentication import AuthenticationStrategy
6
+ from flipt.exceptions import FliptApiError
7
+
8
+ from .models import (
9
+ BatchEvaluationRequest,
10
+ BatchEvaluationResponse,
11
+ BooleanEvaluationResponse,
12
+ EvaluationRequest,
13
+ VariantEvaluationResponse,
14
+ )
15
+
16
+
17
+ class AsyncEvaluation:
18
+ def __init__(
19
+ self,
20
+ url: str,
21
+ authentication: AuthenticationStrategy | None = None,
22
+ httpx_client: httpx.AsyncClient | None = None,
23
+ ):
24
+ self.url = url
25
+ self.headers: dict[str, str] = {}
26
+
27
+ self._client = httpx_client or httpx.AsyncClient()
28
+
29
+ if authentication:
30
+ authentication.authenticate(self.headers)
31
+
32
+ async def close(self) -> None:
33
+ await self._client.aclose()
34
+
35
+ def _raise_on_error(self, response: httpx.Response) -> None:
36
+ if response.status_code != 200:
37
+ body = response.json()
38
+ message = body.get("message", HTTPStatus(response.status_code).description)
39
+ raise FliptApiError(message, response.status_code)
40
+
41
+ async def variant(self, request: EvaluationRequest) -> VariantEvaluationResponse:
42
+ response = await self._client.post(
43
+ f"{self.url}/evaluate/v1/variant",
44
+ headers=self.headers,
45
+ json=request.model_dump(),
46
+ )
47
+
48
+ self._raise_on_error(response)
49
+ return VariantEvaluationResponse.model_validate_json(response.text)
50
+
51
+ async def boolean(self, request: EvaluationRequest) -> BooleanEvaluationResponse:
52
+ response = await self._client.post(
53
+ f"{self.url}/evaluate/v1/boolean",
54
+ headers=self.headers,
55
+ json=request.model_dump(),
56
+ )
57
+
58
+ self._raise_on_error(response)
59
+ return BooleanEvaluationResponse.model_validate_json(response.text)
60
+
61
+ async def batch(self, request: BatchEvaluationRequest) -> BatchEvaluationResponse:
62
+ response = await self._client.post(
63
+ f"{self.url}/evaluate/v1/batch",
64
+ headers=self.headers,
65
+ json=request.model_dump(),
66
+ )
67
+
68
+ self._raise_on_error(response)
69
+ return BatchEvaluationResponse.model_validate_json(response.text)
@@ -0,0 +1,75 @@
1
+ from enum import StrEnum
2
+
3
+ from pydantic import Field
4
+
5
+ from flipt.models import CamelAliasModel
6
+
7
+
8
+ class EvaluationResponseType(StrEnum):
9
+ VARIANT_EVALUATION_RESPONSE_TYPE = "VARIANT_EVALUATION_RESPONSE_TYPE"
10
+ BOOLEAN_EVALUATION_RESPONSE_TYPE = "BOOLEAN_EVALUATION_RESPONSE_TYPE"
11
+ ERROR_EVALUATION_RESPONSE_TYPE = "ERROR_EVALUATION_RESPONSE_TYPE"
12
+
13
+
14
+ class EvaluationReason(StrEnum):
15
+ UNKNOWN_EVALUATION_REASON = "UNKNOWN_EVALUATION_REASON"
16
+ FLAG_DISABLED_EVALUATION_REASON = "FLAG_DISABLED_EVALUATION_REASON"
17
+ MATCH_EVALUATION_REASON = "MATCH_EVALUATION_REASON"
18
+ DEFAULT_EVALUATION_REASON = "DEFAULT_EVALUATION_REASON"
19
+
20
+
21
+ class ErrorEvaluationReason(StrEnum):
22
+ UNKNOWN_ERROR_EVALUATION_REASON = "UNKNOWN_ERROR_EVALUATION_REASON"
23
+ NOT_FOUND_ERROR_EVALUATION_REASON = "NOT_FOUND_ERROR_EVALUATION_REASON"
24
+
25
+
26
+ class EvaluationRequest(CamelAliasModel):
27
+ namespace_key: str = Field(default="default")
28
+ flag_key: str
29
+ entity_id: str
30
+ context: dict
31
+ reference: str | None = None
32
+
33
+
34
+ class BatchEvaluationRequest(CamelAliasModel):
35
+ request_id: str | None = None
36
+ requests: list[EvaluationRequest]
37
+ reference: str | None = None
38
+
39
+
40
+ class VariantEvaluationResponse(CamelAliasModel):
41
+ match: bool
42
+ segment_keys: list[str]
43
+ reason: EvaluationReason
44
+ flag_key: str
45
+ variant_key: str
46
+ variant_attachment: str
47
+ request_duration_millis: float
48
+ timestamp: str
49
+
50
+
51
+ class BooleanEvaluationResponse(CamelAliasModel):
52
+ enabled: bool
53
+ flag_key: str
54
+ reason: EvaluationReason
55
+ request_duration_millis: float
56
+ timestamp: str
57
+
58
+
59
+ class ErrorEvaluationResponse(CamelAliasModel):
60
+ flag_key: str
61
+ namespace_key: str
62
+ reason: ErrorEvaluationReason
63
+
64
+
65
+ class EvaluationResponse(CamelAliasModel):
66
+ type: EvaluationResponseType
67
+ boolean_response: BooleanEvaluationResponse | None = None
68
+ variant_response: VariantEvaluationResponse | None = None
69
+ error_response: ErrorEvaluationResponse | None = None
70
+
71
+
72
+ class BatchEvaluationResponse(CamelAliasModel):
73
+ request_id: str
74
+ responses: list[EvaluationResponse]
75
+ request_duration_millis: float
@@ -0,0 +1,69 @@
1
+ from http import HTTPStatus
2
+
3
+ import httpx
4
+
5
+ from flipt.authentication import AuthenticationStrategy
6
+ from flipt.exceptions import FliptApiError
7
+
8
+ from .models import (
9
+ BatchEvaluationRequest,
10
+ BatchEvaluationResponse,
11
+ BooleanEvaluationResponse,
12
+ EvaluationRequest,
13
+ VariantEvaluationResponse,
14
+ )
15
+
16
+
17
+ class Evaluation:
18
+ def __init__(
19
+ self,
20
+ url: str,
21
+ authentication: AuthenticationStrategy | None = None,
22
+ httpx_client: httpx.Client | None = None,
23
+ ):
24
+ self.url = url
25
+ self.headers: dict[str, str] = {}
26
+
27
+ self._client = httpx_client or httpx.Client()
28
+
29
+ if authentication:
30
+ authentication.authenticate(self.headers)
31
+
32
+ def close(self) -> None:
33
+ self._client.close()
34
+
35
+ def _raise_on_error(self, response: httpx.Response) -> None:
36
+ if response.status_code != 200:
37
+ body = response.json()
38
+ message = body.get("message", HTTPStatus(response.status_code).description)
39
+ raise FliptApiError(message, response.status_code)
40
+
41
+ def variant(self, request: EvaluationRequest) -> VariantEvaluationResponse:
42
+ response = self._client.post(
43
+ f"{self.url}/evaluate/v1/variant",
44
+ headers=self.headers,
45
+ json=request.model_dump(),
46
+ )
47
+
48
+ self._raise_on_error(response)
49
+ return VariantEvaluationResponse.model_validate_json(response.text)
50
+
51
+ def boolean(self, request: EvaluationRequest) -> BooleanEvaluationResponse:
52
+ response = self._client.post(
53
+ f"{self.url}/evaluate/v1/boolean",
54
+ headers=self.headers,
55
+ json=request.model_dump(),
56
+ )
57
+
58
+ self._raise_on_error(response)
59
+ return BooleanEvaluationResponse.model_validate_json(response.text)
60
+
61
+ def batch(self, request: BatchEvaluationRequest) -> BatchEvaluationResponse:
62
+ response = self._client.post(
63
+ f"{self.url}/evaluate/v1/batch",
64
+ headers=self.headers,
65
+ json=request.model_dump(),
66
+ )
67
+
68
+ self._raise_on_error(response)
69
+ return BatchEvaluationResponse.model_validate_json(response.text)
flipt/exceptions.py ADDED
@@ -0,0 +1,14 @@
1
+ class BaseFliptError(Exception):
2
+ pass
3
+
4
+
5
+ class FliptApiError(BaseFliptError):
6
+ message: str
7
+ status_code: int
8
+
9
+ def __init__(self, message: str, status_code: int):
10
+ self.message = message
11
+ self.status_code = status_code
12
+
13
+ def __repr__(self) -> str:
14
+ return f"<FliptApiError(message={self.message}, status_code={self.status_code})>"
@@ -0,0 +1,15 @@
1
+ from .async_flag_client import AsyncFlag
2
+ from .models import (
3
+ Flag,
4
+ FlagType,
5
+ ListFlagsResponse,
6
+ )
7
+ from .sync_flag_client import SyncFlag
8
+
9
+ __all__ = [
10
+ "AsyncFlag",
11
+ "SyncFlag",
12
+ "ListFlagsResponse",
13
+ "Flag",
14
+ "FlagType",
15
+ ]
@@ -0,0 +1,52 @@
1
+ from http import HTTPStatus
2
+
3
+ import httpx
4
+
5
+ from flipt.authentication import AuthenticationStrategy
6
+ from flipt.exceptions import FliptApiError
7
+ from flipt.models import CommonParameters, ListParameters
8
+
9
+ from .models import Flag, ListFlagsResponse
10
+
11
+
12
+ class AsyncFlag:
13
+ def __init__(
14
+ self,
15
+ url: str,
16
+ authentication: AuthenticationStrategy | None = None,
17
+ httpx_client: httpx.AsyncClient | None = None,
18
+ ):
19
+ self.url = url
20
+ self.headers: dict[str, str] = {}
21
+
22
+ self._client = httpx_client or httpx.AsyncClient()
23
+
24
+ if authentication:
25
+ authentication.authenticate(self.headers)
26
+
27
+ async def close(self) -> None:
28
+ await self._client.aclose()
29
+
30
+ def _raise_on_error(self, response: httpx.Response) -> None:
31
+ if response.status_code != 200:
32
+ body = response.json()
33
+ message = body.get("message", HTTPStatus(response.status_code).description)
34
+ raise FliptApiError(message, response.status_code)
35
+
36
+ async def list_flags(self, namespace_key: str, params: ListParameters | None = None) -> ListFlagsResponse:
37
+ response = await self._client.get(
38
+ f"{self.url}/api/v1/namespaces/{namespace_key}/flags",
39
+ params=params.model_dump_json(exclude_none=True, by_alias=True) if params else {},
40
+ headers=self.headers,
41
+ )
42
+ self._raise_on_error(response)
43
+ return ListFlagsResponse.model_validate_json(response.text)
44
+
45
+ async def get_flag(self, namespace_key: str, flag_key: str, params: CommonParameters | None = None) -> Flag:
46
+ response = await self._client.get(
47
+ f"{self.url}/api/v1/namespaces/{namespace_key}/flags/{flag_key}",
48
+ params=params.model_dump_json(exclude_none=True, by_alias=True) if params else {},
49
+ headers=self.headers,
50
+ )
51
+ self._raise_on_error(response)
52
+ return Flag.model_validate_json(response.text)
flipt/flags/models.py ADDED
@@ -0,0 +1,37 @@
1
+ from datetime import datetime
2
+ from enum import StrEnum
3
+
4
+ from flipt.models import CamelAliasModel, PaginatedResponse
5
+
6
+
7
+ class FlagType(StrEnum):
8
+ variant = "VARIANT_FLAG_TYPE"
9
+ boolean = "BOOLEAN_FLAG_TYPE"
10
+
11
+
12
+ class Variant(CamelAliasModel):
13
+ attachment: str
14
+ description: str
15
+ flag_key: str
16
+ id: str
17
+ key: str
18
+ name: str
19
+ namespace_key: str
20
+ created_at: datetime
21
+ updated_at: datetime
22
+
23
+
24
+ class Flag(CamelAliasModel):
25
+ key: str
26
+ name: str
27
+ description: str
28
+ enabled: bool
29
+ namespace_key: str
30
+ type: FlagType
31
+ created_at: datetime
32
+ updated_at: datetime
33
+ variants: list[Variant]
34
+
35
+
36
+ class ListFlagsResponse(PaginatedResponse):
37
+ flags: list[Flag]
@@ -0,0 +1,52 @@
1
+ from http import HTTPStatus
2
+
3
+ import httpx
4
+
5
+ from flipt.authentication import AuthenticationStrategy
6
+ from flipt.exceptions import FliptApiError
7
+ from flipt.models import CommonParameters, ListParameters
8
+
9
+ from .models import Flag, ListFlagsResponse
10
+
11
+
12
+ class SyncFlag:
13
+ def __init__(
14
+ self,
15
+ url: str,
16
+ authentication: AuthenticationStrategy | None = None,
17
+ httpx_client: httpx.Client | None = None,
18
+ ):
19
+ self.url = url
20
+ self.headers: dict[str, str] = {}
21
+
22
+ self._client = httpx_client or httpx.Client()
23
+
24
+ if authentication:
25
+ authentication.authenticate(self.headers)
26
+
27
+ def close(self) -> None:
28
+ self._client.close()
29
+
30
+ def _raise_on_error(self, response: httpx.Response) -> None:
31
+ if response.status_code != 200:
32
+ body = response.json()
33
+ message = body.get("message", HTTPStatus(response.status_code).description)
34
+ raise FliptApiError(message, response.status_code)
35
+
36
+ def list_flags(self, namespace_key: str, params: ListParameters | None = None) -> ListFlagsResponse:
37
+ response = self._client.get(
38
+ f"{self.url}/api/v1/namespaces/{namespace_key}/flags",
39
+ params=params.model_dump_json(exclude_none=True, by_alias=True) if params else {},
40
+ headers=self.headers,
41
+ )
42
+ self._raise_on_error(response)
43
+ return ListFlagsResponse.model_validate_json(response.text)
44
+
45
+ def get_flag(self, namespace_key: str, flag_key: str, params: CommonParameters | None = None) -> Flag:
46
+ response = self._client.get(
47
+ f"{self.url}/api/v1/namespaces/{namespace_key}/flags/{flag_key}",
48
+ params=params.model_dump_json(exclude_none=True, by_alias=True) if params else {},
49
+ headers=self.headers,
50
+ )
51
+ self._raise_on_error(response)
52
+ return Flag.model_validate_json(response.text)
flipt/models.py ADDED
@@ -0,0 +1,24 @@
1
+ from pydantic import AliasGenerator, BaseModel, ConfigDict
2
+ from pydantic.alias_generators import to_camel
3
+
4
+
5
+ class CamelAliasModel(BaseModel):
6
+ model_config = ConfigDict(
7
+ alias_generator=AliasGenerator(alias=to_camel),
8
+ populate_by_name=True,
9
+ )
10
+
11
+
12
+ class CommonParameters(CamelAliasModel):
13
+ reference: str | None = None
14
+
15
+
16
+ class ListParameters(CommonParameters):
17
+ limit: int | None = None
18
+ offset: int | None = None
19
+ page_token: str | None = None
20
+
21
+
22
+ class PaginatedResponse(CamelAliasModel):
23
+ next_page_token: str
24
+ total_count: int
flipt/py.typed ADDED
File without changes
flipt/sync_client.py ADDED
@@ -0,0 +1,21 @@
1
+ import httpx
2
+
3
+ from .authentication import AuthenticationStrategy
4
+ from .evaluation import Evaluation
5
+ from .flags import SyncFlag
6
+
7
+
8
+ class FliptClient:
9
+ def __init__(
10
+ self,
11
+ url: str = "http://localhost:8080",
12
+ timeout: int = 60,
13
+ authentication: AuthenticationStrategy | None = None,
14
+ ):
15
+ self.httpx_client = httpx.Client(timeout=timeout)
16
+
17
+ self.evaluation = Evaluation(url, authentication, self.httpx_client)
18
+ self.flag = SyncFlag(url, authentication, self.httpx_client)
19
+
20
+ def close(self) -> None:
21
+ self.httpx_client.close()
@@ -0,0 +1,64 @@
1
+ Metadata-Version: 2.1
2
+ Name: flipt
3
+ Version: 1.3.0
4
+ Summary: Flipt Server SDK
5
+ License: MIT
6
+ Author: Flipt Devs
7
+ Author-email: dev@flipt.io
8
+ Requires-Python: >=3.11,<4.0
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Requires-Dist: httpx (>=0.27.0,<0.28.0)
14
+ Requires-Dist: pydantic (>=2.5.3,<3.0.0)
15
+ Description-Content-Type: text/markdown
16
+
17
+ # Flipt Python
18
+
19
+ [![pypi](https://img.shields.io/pypi/v/flipt.svg)](https://pypi.org/project/flipt)
20
+
21
+ This directory contains the Python source code for the Flipt [server-side](https://www.flipt.io/docs/integration/server/rest) client.
22
+
23
+ ## Documentation
24
+
25
+ API documentation is available at <https://www.flipt.io/docs/reference/overview>.
26
+
27
+ ## Installation
28
+
29
+ ```sh
30
+ pip install flipt=={version}
31
+ ```
32
+
33
+ ## Usage
34
+
35
+ In your Python code you can import this client and use it as so:
36
+
37
+ ```python
38
+ from flipt import FliptClient
39
+ from flipt.evaluation import BatchEvaluationRequest, EvaluationRequest
40
+
41
+ flipt_client = FliptClient()
42
+
43
+ variant_flag = flipt_client.evaluation.variant(
44
+ EvaluationRequest(
45
+ namespace_key="default",
46
+ flag_key="flagll",
47
+ entity_id="entity",
48
+ context={"fizz": "buzz"},
49
+ )
50
+ )
51
+
52
+ print(variant_flag)
53
+ ```
54
+
55
+ There is a more detailed example in the [examples](./examples) directory.
56
+
57
+
58
+ ## For developers
59
+
60
+ After adding new code, please don't forget to add unit tests for new features.
61
+ To format the code, check it with linters and run tests, use the `make check` command.
62
+
63
+ Please keep the Python [PEP8](https://peps.python.org/pep-0008/) in mind while adding new code.
64
+
@@ -0,0 +1,19 @@
1
+ flipt/__init__.py,sha256=lejYXHyiOU4aFbw2Hu7h2QytrxlGyWjofbUZAKBx9Mw,138
2
+ flipt/async_client.py,sha256=9Du6gIOM1gV2ojZ_wocMVD9KGY6E5stLdTq-Wzv2jws,625
3
+ flipt/authentication/__init__.py,sha256=_DOHmgWYQVLxtJ91kiQ8m-C6z7wuKyqIiPJe69DgfpE,614
4
+ flipt/evaluation/__init__.py,sha256=rDXQHPiww5h3ffISrPdRAWghIQQyxRvaF-Z1aQBlrAw,744
5
+ flipt/evaluation/async_evaluation_client.py,sha256=AZqbw-qehwCGwTigHOv8we-Wowmb7gjqYM6imRQlNXY,2207
6
+ flipt/evaluation/models.py,sha256=xzxSibS52EQ8Q5JmUSPsJrInvUgyLItAeezgXtFzuOw,2108
7
+ flipt/evaluation/sync_evaluation_client.py,sha256=E2bN5Emzm8ZslKWdp79G07hED43CVDzF8YLUU5KRHuc,2143
8
+ flipt/exceptions.py,sha256=8-D9K4yRWRBK0hG9eMehU8Syq4RLfcmHT8E8-j-hl6I,368
9
+ flipt/flags/__init__.py,sha256=94-RL_F8ZKFVfx1DECkM08lvJlHDI2vfHSpXxmSrFfQ,252
10
+ flipt/flags/async_flag_client.py,sha256=ivmInMViYkvKSF-5TrERJ4P_he_yBOVfux2igeuIpMc,1945
11
+ flipt/flags/models.py,sha256=y4SDmaSF3mZOHx-y3au7KzPGXj52PZ6ETCJcMuNUplc,701
12
+ flipt/flags/sync_flag_client.py,sha256=kci-NLNfyYy4RwTG0WSLWbCuXMc19ep9Ri9wsdsZGrg,1897
13
+ flipt/models.py,sha256=oEUte_vQqGzWNOCeQTBFP5MxAWAZEDdkjBVhZLTpDvE,567
14
+ flipt/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ flipt/sync_client.py,sha256=C8SDP1LQ6IgSE2873KdiT5IiagKeinPDrkIa_2szkcE,590
16
+ flipt-1.3.0.dist-info/METADATA,sha256=B_XIaWrs2P2SLFtufcQwqceYCrsdl7gTrw_26bvHBkg,1684
17
+ flipt-1.3.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
18
+ flipt-1.3.0.dist-info/entry_points.txt,sha256=d-DgzOMENNfQ2VNMOztEgFpFxFffuOcwGl6KXc-ThuA,37
19
+ flipt-1.3.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 1.8.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ test=scripts:test
3
+