bb_api 0.1.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.
- bb_api/__init__.py +23 -0
- bb_api/accountability.py +1886 -0
- bb_api/common.py +83 -0
- bb_api/gestao_agil.py +251 -0
- bb_api-0.1.0.dist-info/METADATA +54 -0
- bb_api-0.1.0.dist-info/RECORD +9 -0
- bb_api-0.1.0.dist-info/WHEEL +5 -0
- bb_api-0.1.0.dist-info/licenses/LICENSE +674 -0
- bb_api-0.1.0.dist-info/top_level.txt +1 -0
bb_api/common.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import requests
|
|
3
|
+
import pandas as pd
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from collections.abc import Hashable, Mapping, Sequence
|
|
6
|
+
from datetime import date, datetime, timedelta
|
|
7
|
+
from typing import cast
|
|
8
|
+
|
|
9
|
+
dese_oauth_domain = "https://oauth.desenv.bb.com.br"
|
|
10
|
+
homo_oauth_domain = "https://oauth.hm.bb.com.br"
|
|
11
|
+
homo_alt_oauth_domain = "https://oauth.sandbox.bb.com.br"
|
|
12
|
+
prod_oauth_domain = "https://oauth.bb.com.br"
|
|
13
|
+
|
|
14
|
+
dese_api_domain = "https://api.desenv.bb.com.br"
|
|
15
|
+
homo_api_domain = "https://api.hm.bb.com.br"
|
|
16
|
+
homo_alt_api_domain = "https://api.sandbox.bb.com.br"
|
|
17
|
+
prod_api_domain = "https://api.bb.com.br"
|
|
18
|
+
|
|
19
|
+
time_between_access_token_requests = timedelta(minutes=10)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
type DateLike = str | date | datetime
|
|
23
|
+
type Scalar = str | int | float | bool | None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Ambiente(Enum):
|
|
27
|
+
DESENVOLVIMENTO = 0
|
|
28
|
+
HOMOLOGACAO = 1
|
|
29
|
+
HOMOLOGACAO_ALTERNATIVO = 2
|
|
30
|
+
PRODUCAO = 3
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_headers(access_token: str) -> dict[str, str]:
|
|
34
|
+
return {
|
|
35
|
+
"Authorization": f"Bearer {access_token}",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def handle_numeric_string_with_symbols(v: str) -> str:
|
|
40
|
+
return re.sub(r"\D", "", v)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def handle_dates(v: DateLike) -> str:
|
|
44
|
+
if isinstance(v, str):
|
|
45
|
+
dt = datetime.strptime(v, "%Y-%m-%d")
|
|
46
|
+
elif isinstance(v, datetime):
|
|
47
|
+
dt = v
|
|
48
|
+
else:
|
|
49
|
+
dt = datetime.combine(v, datetime.min.time())
|
|
50
|
+
|
|
51
|
+
return dt.strftime("%Y-%m-%d")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def parse_json_object(res: requests.Response) -> dict[str, object]:
|
|
55
|
+
return cast("dict[str, object]", res.json())
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def handle_results(
|
|
59
|
+
data: object,
|
|
60
|
+
main_list: str | None = None,
|
|
61
|
+
insertables: Sequence[str] | None = None,
|
|
62
|
+
explodeables: Sequence[str] | None = None,
|
|
63
|
+
rename_dict: Mapping[str, str] | None = None,
|
|
64
|
+
) -> pd.DataFrame:
|
|
65
|
+
record = cast("Mapping[str, object]", data)
|
|
66
|
+
|
|
67
|
+
if main_list is not None:
|
|
68
|
+
df = pd.DataFrame(cast("list[dict[Hashable, object]]", record[main_list]))
|
|
69
|
+
else:
|
|
70
|
+
df = pd.DataFrame([dict(record)])
|
|
71
|
+
|
|
72
|
+
if insertables is not None:
|
|
73
|
+
for insertable in insertables:
|
|
74
|
+
df[insertable] = cast("Scalar", record[insertable])
|
|
75
|
+
|
|
76
|
+
if explodeables is not None:
|
|
77
|
+
for explodeable in explodeables:
|
|
78
|
+
df = df.explode(explodeable, ignore_index=True)
|
|
79
|
+
|
|
80
|
+
if rename_dict is not None:
|
|
81
|
+
df = df.rename(rename_dict, axis=1)
|
|
82
|
+
|
|
83
|
+
return df
|
bb_api/gestao_agil.py
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""Parser para arquivos de retorno MCIF470 (abertura de contas massificadas).
|
|
2
|
+
|
|
3
|
+
Esses arquivos são disponibilizados pela API GMT-SIA do Banco do Brasil
|
|
4
|
+
(``gmtedi.bb.com.br/gmt-sia-api``), no contexto do sistema Gestão Ágil, e seguem
|
|
5
|
+
um layout de largura fixa de 150 posições por linha, conforme a aba
|
|
6
|
+
"MCIF470-Abertura RETORNO" da planilha de leiaute de abertura massificada.
|
|
7
|
+
|
|
8
|
+
Cada linha é identificada pelas 5 primeiras posições:
|
|
9
|
+
|
|
10
|
+
HEADER -> "00000"
|
|
11
|
+
TRAILER -> "99999"
|
|
12
|
+
DETALHE -> demais
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
from collections.abc import Mapping, Sequence
|
|
17
|
+
|
|
18
|
+
import pandas as pd
|
|
19
|
+
|
|
20
|
+
import bb_api.common as common
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
_HEADER_ID = "00000"
|
|
24
|
+
_TRAILER_ID = "99999"
|
|
25
|
+
_LINE_LENGTH = 150
|
|
26
|
+
|
|
27
|
+
# Cada campo: (nome, posição inicial, posição final, formato).
|
|
28
|
+
# Posições 1-based e inclusivas, como na planilha de leiaute.
|
|
29
|
+
_HEADER_LAYOUT = [
|
|
30
|
+
("preenchimento", 1, 5, "N"),
|
|
31
|
+
("data_remessa", 6, 13, "N"), # DDMMAAAA
|
|
32
|
+
("nome_arquivo", 14, 21, "A"),
|
|
33
|
+
("numero_processo", 22, 26, "N"),
|
|
34
|
+
("sequencial_remessa", 27, 31, "N"),
|
|
35
|
+
("versao_leiaute", 32, 33, "N"),
|
|
36
|
+
("espacos_em_branco", 34, 150, "A"),
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
_DETALHE_LAYOUT = [
|
|
40
|
+
("sequencial", 1, 5, "N"),
|
|
41
|
+
("cpf_cnpj", 6, 19, "N"),
|
|
42
|
+
("data_nascimento", 20, 27, "N"), # DDMMAAAA
|
|
43
|
+
("nome_cliente", 28, 87, "A"),
|
|
44
|
+
("uso_cliente", 88, 95, "A"),
|
|
45
|
+
("numero_programa_gestao_agil", 96, 104, "A"),
|
|
46
|
+
("agencia_cliente", 105, 108, "N"),
|
|
47
|
+
("dv_agencia_cliente", 109, 109, "N"),
|
|
48
|
+
("grupo_setex", 110, 111, "N"),
|
|
49
|
+
("dv_grupo_setex", 112, 112, "N"),
|
|
50
|
+
("conta", 113, 123, "N"),
|
|
51
|
+
("dv_conta", 124, 124, "N"),
|
|
52
|
+
("ocorrencia_cliente", 125, 127, "N"),
|
|
53
|
+
("ocorrencia_conta", 128, 130, "N"),
|
|
54
|
+
("ocorrencia_limite_credito", 131, 133, "N"),
|
|
55
|
+
("codigo_mci", 134, 142, "N"),
|
|
56
|
+
("espacos_em_branco", 143, 150, "A"),
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
_TRAILER_LAYOUT = [
|
|
60
|
+
("preenchimento", 1, 5, "N"),
|
|
61
|
+
("quantidade_registros", 6, 14, "N"),
|
|
62
|
+
("espacos_em_branco", 15, 150, "A"),
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
# Tabela 1 - Ocorrências do Cliente
|
|
66
|
+
_TABELA_OCORRENCIA_CLIENTE = {
|
|
67
|
+
"001": "tipo pessoa inválido",
|
|
68
|
+
"002": "tipo CPF/CNPJ inválido",
|
|
69
|
+
"003": "CPF/CNPJ inválido",
|
|
70
|
+
"004": "data nascimento invalida",
|
|
71
|
+
"005": "nome cliente inválido",
|
|
72
|
+
"006": "agência/dv inválido",
|
|
73
|
+
"007": "mais de 5 clientes cadastrados para CPF informado",
|
|
74
|
+
"008": "cliente BB-Campus fora da faixa etária (16 a 28 anos)",
|
|
75
|
+
"009": "cliente BBCampus não é pessoa física",
|
|
76
|
+
"010": "dados pessoa física divergentes",
|
|
77
|
+
"011": "dados pessoa jurídica divergentes",
|
|
78
|
+
"013": "cliente BBCampus não titular CPF",
|
|
79
|
+
"014": "perfil agência incompatível com tipo pessoa",
|
|
80
|
+
"015": "tipo de pessoa incompátivel com natureza jurídica",
|
|
81
|
+
"016": "tipo de repasse inválido",
|
|
82
|
+
"017": "tipo de pessoa não permitido para esse processo/tipo de repasse",
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
# Tabela 2 - Ocorrências da Conta
|
|
86
|
+
_TABELA_OCORRENCIA_CONTA = {
|
|
87
|
+
"001": "ind cheque especial inválido",
|
|
88
|
+
"002": "setex/dv inválido",
|
|
89
|
+
"003": "não atende ao credit scoring",
|
|
90
|
+
"004": "dados pessoa física divergente",
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# Tabela 3 - Ocorrências do Limite de Crédito
|
|
94
|
+
_TABELA_OCORRENCIA_LIMITE = {
|
|
95
|
+
"001": "cod estado civil inválido",
|
|
96
|
+
"002": "cod natureza ocupação inválido",
|
|
97
|
+
"003": "cod ocupação inválido",
|
|
98
|
+
"004": "valor rendimento inválido",
|
|
99
|
+
"005": "data rendimento invalida",
|
|
100
|
+
"006": "tipo contrato trabalho inválido",
|
|
101
|
+
"007": "data início emprego inválida",
|
|
102
|
+
"008": "nao atende ao credit scoring",
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# Colunas de preenchimento/controle que não interessam ao DataFrame final.
|
|
106
|
+
_COLUNAS_DESCARTADAS = ["tipo", "espacos_em_branco"]
|
|
107
|
+
|
|
108
|
+
_RENAME_DETALHE = {
|
|
109
|
+
"linha": "Linha",
|
|
110
|
+
"sequencial": "Sequencial",
|
|
111
|
+
"cpf_cnpj": "CPF/CNPJ",
|
|
112
|
+
"data_nascimento": "Data Nascimento",
|
|
113
|
+
"nome_cliente": "Nome Cliente",
|
|
114
|
+
"uso_cliente": "Uso Cliente",
|
|
115
|
+
"numero_programa_gestao_agil": "Número Programa Gestão Ágil",
|
|
116
|
+
"agencia_cliente": "Agência Cliente",
|
|
117
|
+
"dv_agencia_cliente": "DV Agência Cliente",
|
|
118
|
+
"grupo_setex": "Grupo Setex",
|
|
119
|
+
"dv_grupo_setex": "DV Grupo Setex",
|
|
120
|
+
"conta": "Conta",
|
|
121
|
+
"dv_conta": "DV Conta",
|
|
122
|
+
"ocorrencia_cliente": "Código Ocorrência Cliente",
|
|
123
|
+
"ocorrencia_conta": "Código Ocorrência Conta",
|
|
124
|
+
"ocorrencia_limite_credito": "Código Ocorrência Limite Crédito",
|
|
125
|
+
"codigo_mci": "Código MCI",
|
|
126
|
+
"ocorrencia_cliente_desc": "Ocorrência Cliente",
|
|
127
|
+
"ocorrencia_conta_desc": "Ocorrência Conta",
|
|
128
|
+
"ocorrencia_limite_credito_desc": "Ocorrência Limite Crédito",
|
|
129
|
+
"numero_processo": "Número Processo",
|
|
130
|
+
"data_remessa": "Data Remessa",
|
|
131
|
+
"sequencial_remessa": "Sequencial Remessa",
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _parse_fields(
|
|
136
|
+
linha: str,
|
|
137
|
+
layout: Sequence[tuple[str, int, int, str]],
|
|
138
|
+
) -> dict[str, str | int]:
|
|
139
|
+
"""Fatia a linha conforme o layout. Campos "N" viram int quando possível."""
|
|
140
|
+
rec: dict[str, str | int] = {}
|
|
141
|
+
for nome, ini, fim, fmt in layout:
|
|
142
|
+
valor = linha[ini - 1:fim].strip()
|
|
143
|
+
if fmt == "N" and valor.isdigit():
|
|
144
|
+
rec[nome] = int(valor)
|
|
145
|
+
else:
|
|
146
|
+
rec[nome] = valor
|
|
147
|
+
return rec
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _decode_ocorrencia(codigo: object, tabela: Mapping[str, str]) -> str | None:
|
|
151
|
+
"""Descrição da ocorrência; None para "000"/vazio/sem código."""
|
|
152
|
+
cod = str(codigo).zfill(3) if str(codigo).strip() else ""
|
|
153
|
+
if cod in ("", "000"):
|
|
154
|
+
return None
|
|
155
|
+
return tabela.get(cod, "código desconhecido")
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _parse_linha(linha: str, numero: int) -> dict[str, object] | None:
|
|
159
|
+
"""Parseia uma linha e devolve um dict com o tipo de registro e os campos."""
|
|
160
|
+
linha = linha.rstrip("\r\n")
|
|
161
|
+
if not linha.strip():
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
if len(linha) < _LINE_LENGTH:
|
|
165
|
+
linha = linha.ljust(_LINE_LENGTH)
|
|
166
|
+
|
|
167
|
+
ident = linha[:5]
|
|
168
|
+
if ident == _HEADER_ID:
|
|
169
|
+
return {"tipo": "HEADER", "linha": numero, **_parse_fields(linha, _HEADER_LAYOUT)}
|
|
170
|
+
if ident == _TRAILER_ID:
|
|
171
|
+
return {"tipo": "TRAILER", "linha": numero, **_parse_fields(linha, _TRAILER_LAYOUT)}
|
|
172
|
+
|
|
173
|
+
rec: dict[str, object] = {
|
|
174
|
+
"tipo": "DETALHE",
|
|
175
|
+
"linha": numero,
|
|
176
|
+
**_parse_fields(linha, _DETALHE_LAYOUT),
|
|
177
|
+
}
|
|
178
|
+
rec["ocorrencia_cliente_desc"] = _decode_ocorrencia(
|
|
179
|
+
rec["ocorrencia_cliente"], _TABELA_OCORRENCIA_CLIENTE)
|
|
180
|
+
rec["ocorrencia_conta_desc"] = _decode_ocorrencia(
|
|
181
|
+
rec["ocorrencia_conta"], _TABELA_OCORRENCIA_CONTA)
|
|
182
|
+
rec["ocorrencia_limite_credito_desc"] = _decode_ocorrencia(
|
|
183
|
+
rec["ocorrencia_limite_credito"], _TABELA_OCORRENCIA_LIMITE)
|
|
184
|
+
return rec
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def parse_retorno_abertura_massificada(conteudo: str) -> pd.DataFrame:
|
|
188
|
+
"""Parseia o conteúdo de um arquivo de retorno MCIF470 (abertura de contas
|
|
189
|
+
massificadas) e devolve um ``DataFrame`` com os registros de DETALHE.
|
|
190
|
+
|
|
191
|
+
Os códigos de ocorrência são decodificados em colunas descritivas e os
|
|
192
|
+
metadados do cabeçalho (número do processo, data e sequencial da remessa)
|
|
193
|
+
são repetidos em todas as linhas para facilitar a rastreabilidade.
|
|
194
|
+
|
|
195
|
+
Parâmetros
|
|
196
|
+
----------
|
|
197
|
+
conteudo: str
|
|
198
|
+
Conteúdo textual completo do arquivo de retorno.
|
|
199
|
+
"""
|
|
200
|
+
registros: list[dict[str, object]] = []
|
|
201
|
+
for numero, linha in enumerate(conteudo.splitlines(), start=1):
|
|
202
|
+
rec = _parse_linha(linha, numero)
|
|
203
|
+
if rec is not None:
|
|
204
|
+
registros.append(rec)
|
|
205
|
+
|
|
206
|
+
detalhes = [r for r in registros if r["tipo"] == "DETALHE"]
|
|
207
|
+
|
|
208
|
+
header: dict[str, object] = {}
|
|
209
|
+
for registro in registros:
|
|
210
|
+
if registro["tipo"] == "HEADER":
|
|
211
|
+
header = registro
|
|
212
|
+
break
|
|
213
|
+
|
|
214
|
+
data = {
|
|
215
|
+
"detalhes": detalhes,
|
|
216
|
+
"numero_processo": header.get("numero_processo"),
|
|
217
|
+
"data_remessa": header.get("data_remessa"),
|
|
218
|
+
"sequencial_remessa": header.get("sequencial_remessa"),
|
|
219
|
+
}
|
|
220
|
+
df = common.handle_results(
|
|
221
|
+
data,
|
|
222
|
+
main_list="detalhes",
|
|
223
|
+
insertables=[
|
|
224
|
+
"numero_processo",
|
|
225
|
+
"data_remessa",
|
|
226
|
+
"sequencial_remessa",
|
|
227
|
+
],
|
|
228
|
+
rename_dict=_RENAME_DETALHE,
|
|
229
|
+
)
|
|
230
|
+
return df.drop(columns=_COLUNAS_DESCARTADAS, errors="ignore")
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def ler_retorno_abertura_massificada(
|
|
234
|
+
caminho: str | os.PathLike[str],
|
|
235
|
+
encoding: str = "latin-1",
|
|
236
|
+
) -> pd.DataFrame:
|
|
237
|
+
"""Lê um arquivo de retorno MCIF470 do disco e o parseia.
|
|
238
|
+
|
|
239
|
+
Atalho para ``parse_retorno_abertura_massificada`` quando o arquivo já está
|
|
240
|
+
salvo localmente.
|
|
241
|
+
|
|
242
|
+
Parâmetros
|
|
243
|
+
----------
|
|
244
|
+
caminho: str | os.PathLike
|
|
245
|
+
Caminho do arquivo de retorno a ser lido.
|
|
246
|
+
encoding: str
|
|
247
|
+
Codificação do arquivo (padrão: ``latin-1``).
|
|
248
|
+
"""
|
|
249
|
+
with open(caminho, "r", encoding=encoding) as arquivo:
|
|
250
|
+
conteudo = arquivo.read()
|
|
251
|
+
return parse_retorno_abertura_massificada(conteudo)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bb_api
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Wrapper da API do Banco do Brasil.
|
|
5
|
+
Requires-Python: >=3.13
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Dist: pandas>=3.0.1
|
|
9
|
+
Requires-Dist: requests>=2.32.5
|
|
10
|
+
Dynamic: license-file
|
|
11
|
+
|
|
12
|
+
# Biblioteca API BB
|
|
13
|
+
|
|
14
|
+
*Wrapper* da API do Banco do Brasil.
|
|
15
|
+
|
|
16
|
+
## Como instalar?
|
|
17
|
+
|
|
18
|
+
Se você usar o `uv`:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
uv add bb_api
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Se você usar o `pip`:
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
pip install bb_api
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Funcionalidades
|
|
31
|
+
|
|
32
|
+
- Lê os parâmetros `app_key`, `client_id` e `client_secret` das variáveis de
|
|
33
|
+
ambiente `BB_API_APP_KEY`, `BB_API_CLIENT_ID` e `BB_API_CLIENT_SECRET`,
|
|
34
|
+
respectivamente, caso não sejam passadas na instanciação das classes
|
|
35
|
+
- Gera o token de acesso automaticamente, gerando um novo a cada 10 minutos,
|
|
36
|
+
tempo de expiração do token definido pelo Banco do Brasil
|
|
37
|
+
- Separa operaçãos disponíveis aos órgãos de repasse e de controle em classes
|
|
38
|
+
separadas, mantendo as operações comuns aos dois
|
|
39
|
+
- Retorna os resultados das chamadas às APIS em formato de `DataFrame` do
|
|
40
|
+
[`pandas`][pandas]
|
|
41
|
+
- Aceita parâmetros em múltiplos formatos, como:
|
|
42
|
+
- CNPJ e CEP podem estar pontuados ou não
|
|
43
|
+
- Datas podem estar em formato `str`, `date` ou `datetime`
|
|
44
|
+
|
|
45
|
+
## Referências
|
|
46
|
+
|
|
47
|
+
Os documentos utilizados de referência para criação dessa API foram:
|
|
48
|
+
|
|
49
|
+
- [Portal Developers BB]
|
|
50
|
+
- [Documentação Swagger API BB]
|
|
51
|
+
|
|
52
|
+
[pandas]: https://pandas.pydata.org/
|
|
53
|
+
[Portal Developers BB]: https://apoio.developers.bb.com.br/referency/post/641877548600960012b32cd6
|
|
54
|
+
[Documentação Swagger API BB]: https://api.bb.com.br/accountability/v3/swagger
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
bb_api/__init__.py,sha256=K-QN7K2yzWDIJMSXf5RvQmigYv2TyvYJrXQdEyGcYpY,520
|
|
2
|
+
bb_api/accountability.py,sha256=8acRN30vmWydcPf5L7lctnQsq0bAA8QMTrKNap6CM2o,74988
|
|
3
|
+
bb_api/common.py,sha256=A1kOGKHJuF79ZZyDDAdFcctEfzr11Bh0l9KyuFXgeP0,2265
|
|
4
|
+
bb_api/gestao_agil.py,sha256=sEMlVH8CB8A1kkGbAGfy9wF374Jn5FT2jny8Vg9-bAE,8955
|
|
5
|
+
bb_api-0.1.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
6
|
+
bb_api-0.1.0.dist-info/METADATA,sha256=rgAjG_QBO4gy6gAwWTXZ1-glNyKC0X1xM0AjlWTjts8,1588
|
|
7
|
+
bb_api-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
8
|
+
bb_api-0.1.0.dist-info/top_level.txt,sha256=ZMdA0Lx6osrxQHw-iizi8tXTIofFtsg_qPApwC2xPOM,7
|
|
9
|
+
bb_api-0.1.0.dist-info/RECORD,,
|