PYield 0.37.2__tar.gz → 0.37.4__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 (53) hide show
  1. {pyield-0.37.2 → pyield-0.37.4}/PKG-INFO +1 -1
  2. pyield-0.37.4/pyield/__about__.py +1 -0
  3. {pyield-0.37.2 → pyield-0.37.4}/pyield/anbima/__init__.py +2 -2
  4. {pyield-0.37.2 → pyield-0.37.4}/pyield/anbima/tpf.py +97 -94
  5. {pyield-0.37.2 → pyield-0.37.4}/pyield/b3/di1.py +40 -13
  6. pyield-0.37.2/pyield/__about__.py +0 -1
  7. {pyield-0.37.2 → pyield-0.37.4}/.gitignore +0 -0
  8. {pyield-0.37.2 → pyield-0.37.4}/LICENSE +0 -0
  9. {pyield-0.37.2 → pyield-0.37.4}/README.md +0 -0
  10. {pyield-0.37.2 → pyield-0.37.4}/pyield/__init__.py +0 -0
  11. {pyield-0.37.2 → pyield-0.37.4}/pyield/anbima/ettj.py +0 -0
  12. {pyield-0.37.2 → pyield-0.37.4}/pyield/anbima/ima.py +0 -0
  13. {pyield-0.37.2 → pyield-0.37.4}/pyield/anbima/imaq.py +0 -0
  14. {pyield-0.37.2 → pyield-0.37.4}/pyield/anbima/ipca.py +0 -0
  15. {pyield-0.37.2 → pyield-0.37.4}/pyield/b3/__init__.py +0 -0
  16. {pyield-0.37.2 → pyield-0.37.4}/pyield/b3/di_over.py +0 -0
  17. {pyield-0.37.2 → pyield-0.37.4}/pyield/b3/futures/__init__.py +0 -0
  18. {pyield-0.37.2 → pyield-0.37.4}/pyield/b3/futures/common.py +0 -0
  19. {pyield-0.37.2 → pyield-0.37.4}/pyield/b3/futures/historical.py +0 -0
  20. {pyield-0.37.2 → pyield-0.37.4}/pyield/b3/futures/intraday.py +0 -0
  21. {pyield-0.37.2 → pyield-0.37.4}/pyield/b3/futures/xml.py +0 -0
  22. {pyield-0.37.2 → pyield-0.37.4}/pyield/bc/__init__.py +0 -0
  23. {pyield-0.37.2 → pyield-0.37.4}/pyield/bc/auction.py +0 -0
  24. {pyield-0.37.2 → pyield-0.37.4}/pyield/bc/ptax_api.py +0 -0
  25. {pyield-0.37.2 → pyield-0.37.4}/pyield/bc/rates.py +0 -0
  26. {pyield-0.37.2 → pyield-0.37.4}/pyield/bc/repo.py +0 -0
  27. {pyield-0.37.2 → pyield-0.37.4}/pyield/bc/trades_intraday.py +0 -0
  28. {pyield-0.37.2 → pyield-0.37.4}/pyield/bc/trades_monthly.py +0 -0
  29. {pyield-0.37.2 → pyield-0.37.4}/pyield/bc/vna.py +0 -0
  30. {pyield-0.37.2 → pyield-0.37.4}/pyield/bday.py +0 -0
  31. {pyield-0.37.2 → pyield-0.37.4}/pyield/conftest.py +0 -0
  32. {pyield-0.37.2 → pyield-0.37.4}/pyield/data_cache.py +0 -0
  33. {pyield-0.37.2 → pyield-0.37.4}/pyield/date_converter.py +0 -0
  34. {pyield-0.37.2 → pyield-0.37.4}/pyield/fwd.py +0 -0
  35. {pyield-0.37.2 → pyield-0.37.4}/pyield/holidays/__init__.py +0 -0
  36. {pyield-0.37.2 → pyield-0.37.4}/pyield/holidays/br_holidays_new.txt +0 -0
  37. {pyield-0.37.2 → pyield-0.37.4}/pyield/holidays/br_holidays_old.txt +0 -0
  38. {pyield-0.37.2 → pyield-0.37.4}/pyield/holidays/brholidays.py +0 -0
  39. {pyield-0.37.2 → pyield-0.37.4}/pyield/ibge/__init__.py +0 -0
  40. {pyield-0.37.2 → pyield-0.37.4}/pyield/ibge/ipca.py +0 -0
  41. {pyield-0.37.2 → pyield-0.37.4}/pyield/interpolator.py +0 -0
  42. {pyield-0.37.2 → pyield-0.37.4}/pyield/py.typed +0 -0
  43. {pyield-0.37.2 → pyield-0.37.4}/pyield/retry.py +0 -0
  44. {pyield-0.37.2 → pyield-0.37.4}/pyield/tn/__init__.py +0 -0
  45. {pyield-0.37.2 → pyield-0.37.4}/pyield/tn/benchmark.py +0 -0
  46. {pyield-0.37.2 → pyield-0.37.4}/pyield/tn/lft.py +0 -0
  47. {pyield-0.37.2 → pyield-0.37.4}/pyield/tn/ltn.py +0 -0
  48. {pyield-0.37.2 → pyield-0.37.4}/pyield/tn/ntnb.py +0 -0
  49. {pyield-0.37.2 → pyield-0.37.4}/pyield/tn/ntnc.py +0 -0
  50. {pyield-0.37.2 → pyield-0.37.4}/pyield/tn/ntnf.py +0 -0
  51. {pyield-0.37.2 → pyield-0.37.4}/pyield/tn/pre.py +0 -0
  52. {pyield-0.37.2 → pyield-0.37.4}/pyield/tn/tools.py +0 -0
  53. {pyield-0.37.2 → pyield-0.37.4}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PYield
3
- Version: 0.37.2
3
+ Version: 0.37.4
4
4
  Summary: A Python library for analysis of fixed income instruments in Brazil
5
5
  Project-URL: Source, https://github.com/crdcj/PYield
6
6
  Author-email: Carlos Carvalho <cr.cj@outlook.com>
@@ -0,0 +1 @@
1
+ __version__ = "0.37.4"
@@ -2,13 +2,13 @@ from pyield.anbima.ettj import intraday_ettj, last_ettj
2
2
  from pyield.anbima.ima import last_ima
3
3
  from pyield.anbima.imaq import imaq
4
4
  from pyield.anbima.ipca import ipca_projection
5
- from pyield.anbima.tpf import fetch_tpf_data, tpf_data, tpf_fixed_rate_maturities
5
+ from pyield.anbima.tpf import _fetch_tpf_data, tpf_data, tpf_fixed_rate_maturities
6
6
 
7
7
  __all__ = [
8
8
  "last_ima",
9
9
  "imaq",
10
10
  "ipca_projection",
11
- "fetch_tpf_data",
11
+ "_fetch_tpf_data",
12
12
  "tpf_data",
13
13
  "tpf_fixed_rate_maturities",
14
14
  "last_ettj",
@@ -8,6 +8,7 @@ from zoneinfo import ZoneInfo
8
8
  import pandas as pd
9
9
 
10
10
  from pyield import bday
11
+ from pyield.b3 import di1
11
12
  from pyield.bc.ptax_api import ptax
12
13
  from pyield.data_cache import get_cached_dataset
13
14
  from pyield.date_converter import DateScalar, convert_input_dates
@@ -106,7 +107,7 @@ def _process_raw_df(df_raw: pd.DataFrame) -> pd.DataFrame:
106
107
  return df
107
108
 
108
109
 
109
- def _process_dv01(df: pd.DataFrame) -> pd.DataFrame:
110
+ def _add_dv01(df: pd.DataFrame) -> pd.DataFrame:
110
111
  df["BDToMat"] = bday.count(start=df["ReferenceDate"], end=df["MaturityDate"])
111
112
 
112
113
  df["Duration"] = 0.0
@@ -167,6 +168,18 @@ def _add_usd_dv01(df: pd.DataFrame) -> pd.DataFrame:
167
168
  return df
168
169
 
169
170
 
171
+ def _add_di_rate(df: pd.DataFrame) -> pd.DataFrame:
172
+ """Add the DI rate column to the DataFrame."""
173
+ # INSERIR OS DADOS DO DI INTERPOLADO ###
174
+ reference_date = df["ReferenceDate"].iloc[0]
175
+ df["DIRate"] = di1.interpolate_rates(
176
+ dates=reference_date,
177
+ expirations=df["MaturityDate"],
178
+ extrapolate=True,
179
+ )
180
+ return df
181
+
182
+
170
183
  def _reorder_columns(df: pd.DataFrame) -> pd.DataFrame:
171
184
  """Reorder the columns of the DataFrame according to the specified order."""
172
185
  column_order = [
@@ -183,6 +196,7 @@ def _reorder_columns(df: pd.DataFrame) -> pd.DataFrame:
183
196
  "BidRate",
184
197
  "AskRate",
185
198
  "IndicativeRate",
199
+ "DIRate",
186
200
  "StdDev",
187
201
  "LowerBoundRateD0",
188
202
  "UpperBoundRateD0",
@@ -194,65 +208,22 @@ def _reorder_columns(df: pd.DataFrame) -> pd.DataFrame:
194
208
  return df[column_order].copy()
195
209
 
196
210
 
197
- def _select_main_columns(df: pd.DataFrame) -> pd.DataFrame:
198
- """Select only the main columns from the DataFrame."""
199
- main_columns = [
200
- "BondType",
201
- "ReferenceDate",
202
- "MaturityDate",
203
- "BDToMat",
204
- "Duration",
205
- "DV01",
206
- "DV01USD",
207
- "Price",
208
- "BidRate",
209
- "AskRate",
210
- "IndicativeRate",
211
- ]
212
- main_columns = [col for col in main_columns if col in df.columns]
213
- return df[main_columns].copy()
214
-
215
-
216
- def fetch_tpf_data(
217
- date: DateScalar,
218
- bond_type: str | BOND_TYPES | None = None,
219
- all_columns: bool = True,
220
- ) -> pd.DataFrame:
221
- """Fetch and process TPF secondary market data directly from the ANBIMA website.
222
- This is a low-level function intended for internal use or specific backend
223
- jobs. For general use, prefer `tpf_data` which includes caching logic.
224
-
225
- Only the last 5 days of data are available in the ANBIMA website. Data older
226
- than 5 business days is only available through the RTM network.
211
+ def _fetch_tpf_data(date: pd.Timestamp) -> pd.DataFrame:
212
+ """Fetch and process TPF secondary market data directly from the ANBIMA source.
227
213
 
228
- This function retrieves bond market data from the ANBIMA website for a
229
- specified reference date. It handles different file formats based on the date
230
- and attempts to download the data from both member and non-member URLs.
214
+ This is a low-level function intended for internal use. It handles the logic
215
+ of building the correct URL (public or RTM), downloading the data with retries,
216
+ and processing it into a structured DataFrame.
231
217
 
232
218
  Args:
233
- date (DateScalar): The reference date for the data.
234
- bond_type (str, optional): Filter data by bond type.
235
- Defaults to None, which returns data for all bond types.
236
- all_columns (bool, optional): If True, all columns are returned.
237
- Defaults to True. If False, only the main columns are returned.
219
+ date (pd.Timestamp): The reference date for the data.
238
220
 
239
221
  Returns:
240
- pd.DataFrame: A DataFrame containing bond market data.
241
- Returns an empty DataFrame if data is not available for the
242
- specified date (weekends, holidays, unavailable data, or lack of
243
- RTM access for older data).
222
+ pd.DataFrame: A DataFrame containing processed bond market data, or an
223
+ empty DataFrame if data is unavailable or a connection error occurs.
244
224
  """
245
- date = convert_input_dates(date)
246
- date_log = date.strftime("%d/%m/%Y")
247
- today = dt.datetime.now(BZ_TIMEZONE).date()
248
- if date.date() > today:
249
- logger.info(
250
- f"Cannot fetch data for a future date ({date_log}). "
251
- "Returning empty DataFrame."
252
- )
253
- return pd.DataFrame()
254
-
255
225
  file_url = _build_file_url(date)
226
+ date_str = date.strftime("%d/%m/%Y")
256
227
 
257
228
  # --- "FAIL-FAST" PARA EVITAR RETRIES DESNECESSÁRIOS NA RTM ---
258
229
  if ANBIMA_RTM_URL in file_url:
@@ -263,7 +234,7 @@ def fetch_tpf_data(
263
234
  # Se falhar (gaierror = get address info error), não estamos na RTM.
264
235
  # Não adianta prosseguir para a função com retry.
265
236
  logger.warning(
266
- f"Could not resolve RTM host for {date_log}. This is expected if "
237
+ f"Could not resolve RTM host for {date_str}. This is expected if "
267
238
  "you are not on the RTM network. Historical data requires RTM access. "
268
239
  "Returning empty DataFrame."
269
240
  )
@@ -274,40 +245,35 @@ def fetch_tpf_data(
274
245
  df = _read_raw_df(file_url)
275
246
  if df.empty:
276
247
  logger.info(
277
- f"Anbima TPF secondary market data for {date_log} not available. "
248
+ f"Anbima TPF secondary market data for {date_str} not available. "
278
249
  "Returning empty DataFrame."
279
250
  )
280
251
  return df
281
252
  df = _process_raw_df(df)
282
- df = _process_dv01(df)
253
+ df = _add_dv01(df)
283
254
  df = _add_usd_dv01(df)
255
+ df = _add_di_rate(df)
284
256
  df = _reorder_columns(df)
285
257
 
286
- if not all_columns:
287
- df = _select_main_columns(df)
288
-
289
- if bond_type:
290
- norm_bond_type = _bond_type_mapping(bond_type) # noqa
291
- df = df.query("BondType == @norm_bond_type").reset_index(drop=True)
292
258
  return df.sort_values(["BondType", "MaturityDate"]).reset_index(drop=True)
293
259
 
294
260
  except HTTPError as e:
295
261
  if e.code == 404: # noqa
296
262
  logger.info(
297
- f"No Anbima TPF secondary market data for {date_log} (HTTP 404). "
263
+ f"No Anbima TPF secondary market data for {date_str} (HTTP 404). "
298
264
  "Returning empty DataFrame."
299
265
  )
300
266
  return pd.DataFrame()
301
- logger.error(f"HTTP Error fetching data for {date_log} from {file_url}: {e}")
267
+ logger.error(f"HTTP Error fetching data for {date_str} from {file_url}: {e}")
302
268
  raise
303
269
 
304
270
  # Este bloco ainda é útil para outros URLErrors (ex: timeout genuíno na URL pública)
305
271
  except URLError:
306
- logger.exception(f"Network error (URLError) fetching TPF data for {date_log}")
272
+ logger.exception(f"Network error (URLError) fetching TPF data for {date_str}")
307
273
  raise
308
274
 
309
275
  except Exception:
310
- msg = f"An unexpected error occurred fetching TPF data for {date_log}"
276
+ msg = f"An unexpected error occurred fetching TPF data for {date_str}"
311
277
  logger.exception(msg)
312
278
  raise
313
279
 
@@ -315,27 +281,27 @@ def fetch_tpf_data(
315
281
  def tpf_data(
316
282
  date: DateScalar,
317
283
  bond_type: str | None = None,
318
- adj_maturities: bool = False,
284
+ fetch_from_source: bool = False,
319
285
  ) -> pd.DataFrame:
320
- """Retrieve indicative rates for bonds from ANBIMA data.
286
+ """Retrieve TPF secondary market data from ANBIMA.
321
287
 
322
- This function fetches indicative rates for bonds from ANBIMA,
323
- initially attempting to retrieve data from a cached dataset. If the data
324
- is not available in the cache, it fetches it directly from the ANBIMA website.
288
+ This function fetches indicative rates and other data for Brazilian government
289
+ bonds from ANBIMA. By default, it attempts to retrieve data from a local
290
+ cache for performance. If `fetch_from_source` is True, it tries to fetch the data
291
+ directly from the ANBIMA website.
325
292
 
326
293
  Args:
327
- date (DateScalar): The reference date for the rates.
328
- bond_type (str, optional): Filter rates by bond type.
329
- Defaults to None, which returns rates for all bond types.
330
- adj_maturities (bool, optional): Adjust maturity dates to the next
331
- business day. Defaults to False.
294
+ date (DateScalar): The reference date for the data (e.g., '2024-06-14').
295
+ bond_type (str, optional): Filters results by a specific bond type
296
+ (e.g., 'LTN', 'NTN-B'). Defaults to None, returning all bond types.
297
+ fetch_from_source (bool, optional): If True, forces the function to
298
+ bypass the cache and fetch data directly from the source.
299
+ Defaults to False.
332
300
 
333
301
  Returns:
334
- pd.DataFrame: A DataFrame with the following columns:
335
- - BondType: The type of bond.
336
- - MaturityDate: The maturity date of the bond.
337
- - IndicativeRate: The indicative rate of the bond.
338
- - Price: The price (PU) of the bond.
302
+ pd.DataFrame: A DataFrame containing the requested bond data. An empty
303
+ DataFrame is returned if no data is available for the specified date
304
+ (e.g., weekends, holidays, or future dates).
339
305
 
340
306
  Examples:
341
307
  >>> from pyield import anbima
@@ -347,30 +313,67 @@ def tpf_data(
347
313
  3 2025-08-22 LFT 2027-03-01 0.000302 17193.200289
348
314
  4 2025-08-22 LFT 2027-09-01 0.000411 17186.767105
349
315
  ...
316
+
317
+ DataFrame columns:
318
+ - BondType: Tipo do título público (e.g., 'LTN', 'NTN-B').
319
+ - ReferenceDate: Data de referência dos dados.
320
+ - SelicCode: Código do título no SELIC.
321
+ - IssueBaseDate: Data base ou de emissão do título.
322
+ - MaturityDate: Data de vencimento do título.
323
+ - BDToMat: Número de dias úteis entre a data de referência e o vencimento.
324
+ - Duration: Macaulay Duration do título em anos.
325
+ - DV01: Variação financeira no preço do título (em BRL) para uma
326
+ mudança de 1 basis point (0,01%) na taxa de juros.
327
+ - DV01USD: O mesmo que DV01, mas convertido para USD pela PTAX do dia.
328
+ - Price: Preço Unitário (PU) do título na data de referência.
329
+ - BidRate: Taxa de compra em formato decimal (e.g., 0.10 para 10%).
330
+ - AskRate: Taxa de venda em formato decimal.
331
+ - IndicativeRate: Taxa indicativa em formato decimal.
332
+ - DIRate: Taxa DI interpolada para o vencimento do título.
333
+ - StdDev: Desvio padrão da taxa indicativa.
334
+ - LowerBoundRateD0: Limite inferior do intervalo indicativo para D+0.
335
+ - UpperBoundRateD0: Limite superior do intervalo indicativo para D+0.
336
+ - LowerBoundRateD1: Limite inferior do intervalo indicativo para D+1.
337
+ - UpperBoundRateD1: Limite superior do intervalo indicativo para D+1.
338
+ - Criteria: Critério utilizado pela ANBIMA para o cálculo.
339
+
340
+ Notes:
341
+ - Data for the last 5 business days is available on the public ANBIMA
342
+ website.
343
+ - Historical data (older than 5 business days) is only available via
344
+ the RTM network. If you are not connected to RTM, requests for
345
+ older dates will return an empty DataFrame.
350
346
  """
351
- df = get_cached_dataset("tpf")
352
347
  date = convert_input_dates(date)
353
- df = df.query("ReferenceDate == @date").reset_index(drop=True)
354
348
 
355
- if df.empty:
349
+ date_log = date.strftime("%d/%m/%Y")
350
+ today = dt.datetime.now(BZ_TIMEZONE).date()
351
+ if date.date() > today:
352
+ logger.info(
353
+ f"Cannot fetch data for a future date ({date_log}). "
354
+ "Returning empty DataFrame."
355
+ )
356
+ return pd.DataFrame()
357
+
358
+ if fetch_from_source:
356
359
  # Try to fetch the data from the Anbima website
357
- df = fetch_tpf_data(date)
360
+ df = _fetch_tpf_data(date)
361
+ else:
362
+ df = (
363
+ get_cached_dataset("tpf")
364
+ .query("ReferenceDate == @date")
365
+ .reset_index(drop=True)
366
+ )
358
367
 
359
368
  if df.empty:
360
369
  # If the data is still empty, return an empty DataFrame
361
370
  return pd.DataFrame()
362
371
 
363
372
  if bond_type:
364
- df = df.query("BondType == @bond_type").reset_index(drop=True)
373
+ norm_bond_type = _bond_type_mapping(bond_type) # noqa
374
+ df = df.query("BondType == @norm_bond_type").reset_index(drop=True)
365
375
 
366
- if adj_maturities:
367
- df["MaturityDate"] = bday.offset(df["MaturityDate"], 0)
368
-
369
- return (
370
- df[["ReferenceDate", "BondType", "MaturityDate", "IndicativeRate", "Price"]]
371
- .sort_values(["BondType", "MaturityDate"])
372
- .reset_index(drop=True)
373
- )
376
+ return df.sort_values(["BondType", "MaturityDate"]).reset_index(drop=True)
374
377
 
375
378
 
376
379
  def tpf_fixed_rate_maturities(date: DateScalar) -> pd.Series:
@@ -14,6 +14,41 @@ logger = logging.getLogger(__name__)
14
14
  TIMEZONE_BZ = ZoneInfo("America/Sao_Paulo")
15
15
 
16
16
 
17
+ def _get_data(dates: pd.Series) -> pd.DataFrame:
18
+ """Busca dados de DI, incluindo dados intraday para o dia corrente se necessário."""
19
+ # 1. Busca inicial no cache com as datas solicitadas pelo usuário.
20
+ df_cached = (
21
+ get_cached_dataset("di1").query("TradeDate in @dates").reset_index(drop=True)
22
+ )
23
+
24
+ today = dt.now(TIMEZONE_BZ).date()
25
+ last_bday = bday.last_business_day().date()
26
+
27
+ # 2. Lógica para buscar dados intraday.
28
+ # Isso é necessário quando o usuário solicita os dados do dia corrente
29
+ # e eles ainda não foram persistidos no cache (processo noturno).
30
+ if (
31
+ # Condição 1: O dia de hoje foi explicitamente solicitado.
32
+ today in dates.values
33
+ # Condição 2: E ainda não está no cache.
34
+ and today not in df_cached["TradeDate"].values
35
+ # Condição 3: E hoje é o último dia útil.
36
+ and today == last_bday
37
+ ):
38
+ try:
39
+ df_intraday = b3.futures(contract_code="DI1", date=today)
40
+ if not df_intraday.empty:
41
+ logger.info("Dados intraday de DI inseridos com sucesso.")
42
+ return pd.concat([df_cached, df_intraday]).reset_index(drop=True)
43
+ else:
44
+ logger.warning(f"Nenhum dado intraday de DI encontrado para {today}.")
45
+ except Exception as e:
46
+ logger.error(f"Falha ao buscar dados intraday para {today}: {e}")
47
+
48
+ # 3. Se a lógica intraday não for acionada ou falhar, retorna apenas dados do cache.
49
+ return df_cached
50
+
51
+
17
52
  def data(
18
53
  dates: DateScalar | DateArray,
19
54
  month_start: bool = False,
@@ -57,22 +92,14 @@ def data(
57
92
  """
58
93
  dates = dc.convert_input_dates(dates)
59
94
  # Force dates to be a Series for consistency
60
- dates = pd.Series(dates) if isinstance(dates, pd.Timestamp) else dates
95
+ if isinstance(dates, pd.Timestamp):
96
+ dates = pd.Series([dates]).astype("date32[pyarrow]")
97
+
61
98
  # Remove duplicates and sort dates
62
99
  dates = dates.drop_duplicates().sort_values().reset_index(drop=True)
63
-
64
- df = get_cached_dataset("di1").query("TradeDate in @dates").reset_index(drop=True)
65
-
66
- today = dt.now(TIMEZONE_BZ).date()
67
- last_bday = bday.last_business_day()
68
- is_trading_day = today == last_bday.date()
69
- if is_trading_day and last_bday in df["TradeDate"]:
70
- logger.info("Trying to get DI Futures data for today.")
71
- df_intraday = b3.futures(contract_code="DI1", date=last_bday)
72
- df = pd.concat([df, df_intraday]).reset_index(drop=True)
73
-
100
+ df = _get_data(dates)
74
101
  if df.empty:
75
- logger.warning("No DI Futures data found for the specified dates.")
102
+ logger.warning("No DI1 data found for the specified dates.")
76
103
  logger.warning("Returning empty DataFrame.")
77
104
  return pd.DataFrame()
78
105
 
@@ -1 +0,0 @@
1
- __version__ = "0.37.2"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes