exa-py 1.0.1__tar.gz → 1.0.5__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.

Potentially problematic release.


This version of exa-py might be problematic. Click here for more details.

exa_py-1.0.5/PKG-INFO ADDED
@@ -0,0 +1,113 @@
1
+ Metadata-Version: 2.1
2
+ Name: exa_py
3
+ Version: 1.0.5
4
+ Summary: Python SDK for Exa API.
5
+ Home-page: https://github.com/exa-labs/exa-py
6
+ Author: Exa
7
+ Author-email: hello@exa.ai
8
+ Classifier: Development Status :: 5 - Production/Stable
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Typing :: Typed
12
+ Classifier: Programming Language :: Python :: 3.7
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Description-Content-Type: text/markdown
19
+
20
+ # Exa
21
+
22
+ Exa API in Python
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ pip install exa_py
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ Import the package and initialize the Exa client with your API key:
33
+
34
+ ```python
35
+ from exa_py import Exa
36
+
37
+ exa = Exa(api_key="your-api-key")
38
+ ```
39
+
40
+ ## Search Request
41
+
42
+ ```python
43
+
44
+ response = exa.search("funny article about silicon valley tech culture",
45
+ num_results=5,
46
+ include_domains=["nytimes.com", "wsj.com"],
47
+ start_published_date="2023-06-12"
48
+ )
49
+
50
+ for result in response.results:
51
+ print(result.title, result.url)
52
+ ```
53
+
54
+ ## Find Similar
55
+
56
+ ```python
57
+ response = exa.find_similar("https://waitbutwhy.com/2014/05/fermi-paradox.html", num_results=5)
58
+
59
+ for result in response.results:
60
+ print(result.title, result.url)
61
+ ```
62
+
63
+ ## Retrieve Document Contents
64
+
65
+ ```python
66
+ ids = ["8U71IlQ5DUTdsZFherhhYA", "X3wd0PbJmAvhu_DQjDKA7A"]
67
+ response = exa.get_contents(ids)
68
+
69
+ for content in response.contents:
70
+ print(content.title, content.url)
71
+ ```
72
+
73
+ ## Reference
74
+
75
+ ### `exa.search()`
76
+
77
+ This function performs a search on the Exa API.
78
+
79
+ #### Args
80
+
81
+ - query (str): The search query.
82
+ - **options**: Additional search options. Valid options are:
83
+ - `num_results` (int): The number of search results to return.
84
+ - `include_domains` (list): A list of domains to include in the search.
85
+ - `exclude_domains` (list): A list of domains to exclude from the search.
86
+ - `start_crawl_date` (str): The start date for the crawl (in YYYY-MM-DD format).
87
+ - `end_crawl_date` (str): The end date for the crawl (in YYYY-MM-DD format).
88
+ - `start_published_date` (str): The start date for when the document was published (in YYYY-MM-DD format).
89
+ - `end_published_date` (str): The end date for when the document was published (in YYYY-MM-DD format).
90
+ - `use_autoprompt` (bool): Whether to use autoprompt for the search.
91
+ - `type` (str): The type of search, 'keyword' or 'neural'. Default: neural
92
+
93
+ #### Returns
94
+ `SearchResponse`: A dataclass containing the search results.
95
+
96
+ ### `exa.find_similar()`
97
+
98
+ #### Args:
99
+ - url (str): The base url to find similar links with.
100
+ - **options**: Additional search options. Valid options are:
101
+ - `num_results` (int): The number of search results to return.
102
+ - `include_domains` (list): A list of domains to include in the search.
103
+ - `exclude_domains` (list): A list of domains to exclude from the search.
104
+ - `start_crawl_date` (str): The start date for the crawl (in YYYY-MM-DD format).
105
+ - `end_crawl_date` (str): The end date for the crawl (in YYYY-MM-DD format).
106
+ - `start_published_date` (str): The start date for when the document was published (in YYYY-MM-DD format).
107
+ - `end_published_date` (str): The end date for when the document was published (in YYYY-MM-DD format).
108
+
109
+ #### Returns
110
+ `SearchResponse`: A dataclass containing the search results.
111
+
112
+ # Contribution
113
+ Contributions to exa-py are very welcome! Feel free to submit pull requests or raise issues.
@@ -1,4 +1,6 @@
1
1
  from __future__ import annotations
2
+ from dataclasses import dataclass
3
+ import dataclasses
2
4
  import re
3
5
  import requests
4
6
  from typing import (
@@ -13,6 +15,7 @@ from typing import (
13
15
  Literal,
14
16
  )
15
17
 
18
+
16
19
  def snake_to_camel(snake_str: str) -> str:
17
20
  """Convert snake_case string to camelCase.
18
21
 
@@ -25,6 +28,7 @@ def snake_to_camel(snake_str: str) -> str:
25
28
  components = snake_str.split("_")
26
29
  return components[0] + "".join(x.title() for x in components[1:])
27
30
 
31
+
28
32
  def to_camel_case(data: dict) -> dict:
29
33
  """Convert keys in a dictionary from snake_case to camelCase.
30
34
 
@@ -36,6 +40,7 @@ def to_camel_case(data: dict) -> dict:
36
40
  """
37
41
  return {snake_to_camel(k): v for k, v in data.items() if v is not None}
38
42
 
43
+
39
44
  def camel_to_snake(camel_str: str) -> str:
40
45
  """Convert camelCase string to snake_case.
41
46
 
@@ -48,6 +53,7 @@ def camel_to_snake(camel_str: str) -> str:
48
53
  snake_str = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", camel_str)
49
54
  return re.sub("([a-z0-9])([A-Z])", r"\1_\2", snake_str).lower()
50
55
 
56
+
51
57
  def to_snake_case(data: dict) -> dict:
52
58
  """Convert keys in a dictionary from camelCase to snake_case.
53
59
 
@@ -59,8 +65,6 @@ def to_snake_case(data: dict) -> dict:
59
65
  """
60
66
  return {camel_to_snake(k): v for k, v in data.items()}
61
67
 
62
- from dataclasses import dataclass
63
-
64
68
  SEARCH_OPTIONS_TYPES = {
65
69
  "query": str, # The query string.
66
70
  "num_results": int, # Number of results (Default: 10, Max for basic: 10).
@@ -88,7 +92,8 @@ FIND_SIMILAR_OPTIONS_TYPES = {
88
92
  "category": str,
89
93
  }
90
94
 
91
- CONTENTS_OPTIONS_TYPES = {"ids": list, "text": dict | bool, "highlights": dict | bool}
95
+ CONTENTS_OPTIONS_TYPES = {"ids": list, "text": Union[dict, bool], "highlights": Union[dict, bool]}
96
+
92
97
 
93
98
  def validate_search_options(
94
99
  options: Dict[str, Optional[object]], expected: dict
@@ -115,10 +120,10 @@ class TextContentsOptions(TypedDict, total=False):
115
120
  """A class representing the options that you can specify when requesting text
116
121
 
117
122
  Attributes:
118
- max_length (int): The maximum number of characters to return. Default: None (no limit).
123
+ max_characters (int): The maximum number of characters to return. Default: None (no limit).
119
124
  include_html_tags (bool): If true, include HTML tags in the returned text. Default false.
120
125
  """
121
- max_length: int
126
+ max_characters: int
122
127
  include_html_tags: bool
123
128
 
124
129
 
@@ -130,6 +135,7 @@ class HighlightsContentsOptions(TypedDict, total=False):
130
135
  num_sentences (int): Size of highlights to return, in sentences. Default: 5
131
136
  highlights_per_url (int): The number of highlights to return per URL. Default: 1
132
137
  """
138
+
133
139
  query: str
134
140
  num_sentences: int
135
141
  highlights_per_url: int
@@ -148,12 +154,12 @@ class _Result:
148
154
  author (str, optional): If available, the author of the content.
149
155
  """
150
156
 
151
- title: str
152
157
  url: str
153
158
  id: str
154
- score: Optional[float]
155
- published_date: Optional[str]
156
- author: Optional[str]
159
+ title: Optional[str] = None
160
+ score: Optional[float] = None
161
+ published_date: Optional[str] = None
162
+ author: Optional[str] = None
157
163
 
158
164
  def __str__(self):
159
165
  return (
@@ -198,7 +204,8 @@ class ResultWithText(_Result):
198
204
  Attributes:
199
205
  text (str): The text of the search result page.
200
206
  """
201
- text: str
207
+
208
+ text: str = dataclasses.field(default_factory=str)
202
209
 
203
210
  def __str__(self):
204
211
  base_str = super().__str__()
@@ -214,8 +221,9 @@ class ResultWithHighlights(_Result):
214
221
  highlights (List[str]): The highlights of the search result.
215
222
  highlight_scores (List[float]): The scores of the highlights of the search result.
216
223
  """
217
- highlights: List[str]
218
- highlight_scores: List[float]
224
+
225
+ highlights: List[str] = dataclasses.field(default_factory=list)
226
+ highlight_scores: List[float] = dataclasses.field(default_factory=list)
219
227
 
220
228
  def __str__(self):
221
229
  base_str = super().__str__()
@@ -235,9 +243,10 @@ class ResultWithTextAndHighlights(_Result):
235
243
  highlights (List[str): The highlights of the search result.
236
244
  highlight_scores (List[float]): The scores of the highlights of the search result.
237
245
  """
238
- text: str
239
- highlights: List[str]
240
- highlight_scores: List[float]
246
+
247
+ text: str = dataclasses.field(default_factory=str)
248
+ highlights: List[str] = dataclasses.field(default_factory=list)
249
+ highlight_scores: List[float] = dataclasses.field(default_factory=list)
241
250
 
242
251
  def __str__(self):
243
252
  base_str = super().__str__()
@@ -257,16 +266,16 @@ class SearchResponse(Generic[T]):
257
266
 
258
267
  Attributes:
259
268
  results (List[Result]): A list of search results.
260
- autopromptString (str, optional): The Exa query created by the autoprompt functionality.
269
+ autoprompt_string (str, optional): The Exa query created by the autoprompt functionality.
261
270
  """
262
271
 
263
272
  results: List[T]
264
- autopromptString: Optional[str]
273
+ autoprompt_string: Optional[str]
265
274
 
266
275
  def __str__(self):
267
276
  output = "\n\n".join(str(result) for result in self.results)
268
- if self.autopromptString:
269
- output += f"\n\nAutoprompt String: {self.autopromptString}"
277
+ if self.autoprompt_string:
278
+ output += f"\n\nAutoprompt String: {self.autoprompt_string}"
270
279
  return output
271
280
 
272
281
 
@@ -290,7 +299,7 @@ def nest_fields(original_dict: Dict, fields_to_nest: List[str], new_key: str):
290
299
  class Exa:
291
300
  """A client for interacting with Exa API."""
292
301
 
293
- def __init__(self, api_key: Optional[str], base_url: str = "https://api.exa.ai"):
302
+ def __init__(self, api_key: Optional[str], base_url: str = "https://api.exa.ai", user_agent: str = "metaphor-python 1.0.2"):
294
303
  """Initialize the Exa client with the provided API key and optional base URL and user agent.
295
304
 
296
305
  Args:
@@ -306,7 +315,7 @@ class Exa:
306
315
  "API key must be provided as argument or in EXA_API_KEY environment variable"
307
316
  )
308
317
  self.base_url = base_url
309
- self.headers = {"x-api-key": api_key}
318
+ self.headers = {"x-api-key": api_key, "User-Agent": user_agent}
310
319
 
311
320
  def request(self, endpoint: str, data):
312
321
  res = requests.post(self.base_url + endpoint, json=data, headers=self.headers)
@@ -362,8 +371,6 @@ class Exa:
362
371
  self,
363
372
  query: str,
364
373
  *,
365
- text: Optional[Literal[False]] = None,
366
- highlights: Optional[Literal[False]] = None,
367
374
  num_results: Optional[int] = None,
368
375
  include_domains: Optional[List[str]] = None,
369
376
  exclude_domains: Optional[List[str]] = None,
@@ -374,7 +381,7 @@ class Exa:
374
381
  use_autoprompt: Optional[bool] = None,
375
382
  type: Optional[str] = None,
376
383
  category: Optional[str] = None,
377
- ) -> SearchResponse[_Result]:
384
+ ) -> SearchResponse[ResultWithText]:
378
385
  ...
379
386
 
380
387
  @overload
@@ -383,7 +390,6 @@ class Exa:
383
390
  query: str,
384
391
  *,
385
392
  text: Union[TextContentsOptions, Literal[True]],
386
- highlights: Optional[Literal[False]] = None,
387
393
  num_results: Optional[int] = None,
388
394
  include_domains: Optional[List[str]] = None,
389
395
  exclude_domains: Optional[List[str]] = None,
@@ -402,7 +408,6 @@ class Exa:
402
408
  self,
403
409
  query: str,
404
410
  *,
405
- text: Optional[Literal[False]] = None,
406
411
  highlights: Union[HighlightsContentsOptions, Literal[True]],
407
412
  num_results: Optional[int] = None,
408
413
  include_domains: Optional[List[str]] = None,
@@ -443,6 +448,8 @@ class Exa:
443
448
  for k, v in {"query": query, **kwargs}.items()
444
449
  if k != "self" and v is not None
445
450
  }
451
+ if "text" not in options and "highlights" not in options:
452
+ options["text"] = True
446
453
  validate_search_options(
447
454
  options, {**SEARCH_OPTIONS_TYPES, **CONTENTS_OPTIONS_TYPES}
448
455
  )
@@ -455,37 +462,32 @@ class Exa:
455
462
  )
456
463
 
457
464
  @overload
458
- def contents(
465
+ def get_contents(
459
466
  self,
460
467
  ids: Union[str, List[str], List[_Result]],
461
- *,
462
- text: Optional[Literal[False]] = None,
463
- highlights: Optional[Literal[False]] = None,
464
- ) -> SearchResponse[_Result]:
468
+ ) -> SearchResponse[ResultWithText]:
465
469
  ...
466
470
 
467
471
  @overload
468
- def contents(
472
+ def get_contents(
469
473
  self,
470
474
  ids: Union[str, List[str], List[_Result]],
471
475
  *,
472
476
  text: Union[TextContentsOptions, Literal[True]],
473
- highlights: Optional[Literal[False]] = None,
474
477
  ) -> SearchResponse[ResultWithText]:
475
478
  ...
476
479
 
477
480
  @overload
478
- def contents(
481
+ def get_contents(
479
482
  self,
480
483
  ids: Union[str, List[str], List[_Result]],
481
484
  *,
482
- text: Optional[Literal[False]] = None,
483
485
  highlights: Union[HighlightsContentsOptions, Literal[True]],
484
486
  ) -> SearchResponse[ResultWithHighlights]:
485
487
  ...
486
488
 
487
489
  @overload
488
- def contents(
490
+ def get_contents(
489
491
  self,
490
492
  ids: Union[str, List[str], List[_Result]],
491
493
  *,
@@ -494,12 +496,14 @@ class Exa:
494
496
  ) -> SearchResponse[ResultWithTextAndHighlights]:
495
497
  ...
496
498
 
497
- def contents(self, ids: Union[str, List[str], List[_Result]], **kwargs):
499
+ def get_contents(self, ids: Union[str, List[str], List[_Result]], **kwargs):
498
500
  options = {
499
501
  k: v
500
502
  for k, v in {"ids": ids, **kwargs}.items()
501
503
  if k != "self" and v is not None
502
504
  }
505
+ if "text" not in options and "highlights" not in options:
506
+ options["text"] = True
503
507
  validate_search_options(options, {**CONTENTS_OPTIONS_TYPES})
504
508
  options = to_camel_case(options)
505
509
  data = self.request("/contents", options)
@@ -536,8 +540,6 @@ class Exa:
536
540
  self,
537
541
  url: str,
538
542
  *,
539
- text: Optional[Literal[False]] = None,
540
- highlights: Optional[Literal[False]] = None,
541
543
  num_results: Optional[int] = None,
542
544
  include_domains: Optional[List[str]] = None,
543
545
  exclude_domains: Optional[List[str]] = None,
@@ -547,7 +549,7 @@ class Exa:
547
549
  end_published_date: Optional[str] = None,
548
550
  exclude_source_domain: Optional[bool] = None,
549
551
  category: Optional[str] = None,
550
- ) -> SearchResponse[_Result]:
552
+ ) -> SearchResponse[ResultWithText]:
551
553
  ...
552
554
 
553
555
  @overload
@@ -556,7 +558,6 @@ class Exa:
556
558
  url: str,
557
559
  *,
558
560
  text: Union[TextContentsOptions, Literal[True]],
559
- highlights: Optional[Literal[False]] = None,
560
561
  num_results: Optional[int] = None,
561
562
  include_domains: Optional[List[str]] = None,
562
563
  exclude_domains: Optional[List[str]] = None,
@@ -574,7 +575,6 @@ class Exa:
574
575
  self,
575
576
  url: str,
576
577
  *,
577
- text: Optional[Literal[False]] = None,
578
578
  highlights: Union[HighlightsContentsOptions, Literal[True]],
579
579
  num_results: Optional[int] = None,
580
580
  include_domains: Optional[List[str]] = None,
@@ -613,6 +613,8 @@ class Exa:
613
613
  for k, v in {"url": url, **kwargs}.items()
614
614
  if k != "self" and v is not None
615
615
  }
616
+ if "text" not in options and "highlights" not in options:
617
+ options["text"] = True
616
618
  validate_search_options(
617
619
  options, {**FIND_SIMILAR_OPTIONS_TYPES, **CONTENTS_OPTIONS_TYPES}
618
620
  )
@@ -0,0 +1,113 @@
1
+ Metadata-Version: 2.1
2
+ Name: exa-py
3
+ Version: 1.0.5
4
+ Summary: Python SDK for Exa API.
5
+ Home-page: https://github.com/exa-labs/exa-py
6
+ Author: Exa
7
+ Author-email: hello@exa.ai
8
+ Classifier: Development Status :: 5 - Production/Stable
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Typing :: Typed
12
+ Classifier: Programming Language :: Python :: 3.7
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Description-Content-Type: text/markdown
19
+
20
+ # Exa
21
+
22
+ Exa API in Python
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ pip install exa_py
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ Import the package and initialize the Exa client with your API key:
33
+
34
+ ```python
35
+ from exa_py import Exa
36
+
37
+ exa = Exa(api_key="your-api-key")
38
+ ```
39
+
40
+ ## Search Request
41
+
42
+ ```python
43
+
44
+ response = exa.search("funny article about silicon valley tech culture",
45
+ num_results=5,
46
+ include_domains=["nytimes.com", "wsj.com"],
47
+ start_published_date="2023-06-12"
48
+ )
49
+
50
+ for result in response.results:
51
+ print(result.title, result.url)
52
+ ```
53
+
54
+ ## Find Similar
55
+
56
+ ```python
57
+ response = exa.find_similar("https://waitbutwhy.com/2014/05/fermi-paradox.html", num_results=5)
58
+
59
+ for result in response.results:
60
+ print(result.title, result.url)
61
+ ```
62
+
63
+ ## Retrieve Document Contents
64
+
65
+ ```python
66
+ ids = ["8U71IlQ5DUTdsZFherhhYA", "X3wd0PbJmAvhu_DQjDKA7A"]
67
+ response = exa.get_contents(ids)
68
+
69
+ for content in response.contents:
70
+ print(content.title, content.url)
71
+ ```
72
+
73
+ ## Reference
74
+
75
+ ### `exa.search()`
76
+
77
+ This function performs a search on the Exa API.
78
+
79
+ #### Args
80
+
81
+ - query (str): The search query.
82
+ - **options**: Additional search options. Valid options are:
83
+ - `num_results` (int): The number of search results to return.
84
+ - `include_domains` (list): A list of domains to include in the search.
85
+ - `exclude_domains` (list): A list of domains to exclude from the search.
86
+ - `start_crawl_date` (str): The start date for the crawl (in YYYY-MM-DD format).
87
+ - `end_crawl_date` (str): The end date for the crawl (in YYYY-MM-DD format).
88
+ - `start_published_date` (str): The start date for when the document was published (in YYYY-MM-DD format).
89
+ - `end_published_date` (str): The end date for when the document was published (in YYYY-MM-DD format).
90
+ - `use_autoprompt` (bool): Whether to use autoprompt for the search.
91
+ - `type` (str): The type of search, 'keyword' or 'neural'. Default: neural
92
+
93
+ #### Returns
94
+ `SearchResponse`: A dataclass containing the search results.
95
+
96
+ ### `exa.find_similar()`
97
+
98
+ #### Args:
99
+ - url (str): The base url to find similar links with.
100
+ - **options**: Additional search options. Valid options are:
101
+ - `num_results` (int): The number of search results to return.
102
+ - `include_domains` (list): A list of domains to include in the search.
103
+ - `exclude_domains` (list): A list of domains to exclude from the search.
104
+ - `start_crawl_date` (str): The start date for the crawl (in YYYY-MM-DD format).
105
+ - `end_crawl_date` (str): The end date for the crawl (in YYYY-MM-DD format).
106
+ - `start_published_date` (str): The start date for when the document was published (in YYYY-MM-DD format).
107
+ - `end_published_date` (str): The end date for when the document was published (in YYYY-MM-DD format).
108
+
109
+ #### Returns
110
+ `SearchResponse`: A dataclass containing the search results.
111
+
112
+ # Contribution
113
+ Contributions to exa-py are very welcome! Feel free to submit pull requests or raise issues.
@@ -2,8 +2,10 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name='exa_py',
5
- version='1.0.1',
5
+ version='1.0.5',
6
6
  description='Python SDK for Exa API.',
7
+ long_description_content_type='text/markdown',
8
+ long_description=open('README.md').read(),
7
9
  author='Exa',
8
10
  author_email='hello@exa.ai',
9
11
  package_data={"exa_py": ["py.typed"]},
@@ -17,7 +19,11 @@ setup(
17
19
  'Intended Audience :: Developers',
18
20
  'License :: OSI Approved :: MIT License',
19
21
  "Typing :: Typed",
22
+ 'Programming Language :: Python :: 3.7',
20
23
  'Programming Language :: Python :: 3.8',
21
24
  'Programming Language :: Python :: 3.9',
25
+ 'Programming Language :: Python :: 3.10',
26
+ 'Programming Language :: Python :: 3.11',
27
+ 'Programming Language :: Python :: 3.12',
22
28
  ],
23
29
  )
exa_py-1.0.1/PKG-INFO DELETED
@@ -1,13 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: exa_py
3
- Version: 1.0.1
4
- Summary: Python SDK for Exa API.
5
- Home-page: https://github.com/exa-labs/exa-py
6
- Author: Exa
7
- Author-email: hello@exa.ai
8
- Classifier: Development Status :: 5 - Production/Stable
9
- Classifier: Intended Audience :: Developers
10
- Classifier: License :: OSI Approved :: MIT License
11
- Classifier: Typing :: Typed
12
- Classifier: Programming Language :: Python :: 3.8
13
- Classifier: Programming Language :: Python :: 3.9
@@ -1,13 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: exa-py
3
- Version: 1.0.1
4
- Summary: Python SDK for Exa API.
5
- Home-page: https://github.com/exa-labs/exa-py
6
- Author: Exa
7
- Author-email: hello@exa.ai
8
- Classifier: Development Status :: 5 - Production/Stable
9
- Classifier: Intended Audience :: Developers
10
- Classifier: License :: OSI Approved :: MIT License
11
- Classifier: Typing :: Typed
12
- Classifier: Programming Language :: Python :: 3.8
13
- Classifier: Programming Language :: Python :: 3.9
File without changes
File without changes
File without changes