Skip to content

Commit bc75b48

Browse files
[infra] Adiciona testes e corrige o traceback da função download (#626) (#649)
* Fix download traceback try-except order, add tests * Fix invalid billing project id test, add syntax error test * Remove placeholders and unused objects * Move query and billing_id tests to test_read_sql_* * Add specific tutorial exceptions to possible download errors * Fix read_sql exceptions * Restructure Exception system * Fix Exception new structure Co-authored-by: Vítor Mussa <vtrmussa@gmail.com>
1 parent 4807121 commit bc75b48

File tree

6 files changed

+181
-53
lines changed

6 files changed

+181
-53
lines changed

python-package/basedosdados/download/download.py

Lines changed: 25 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,19 @@
22
import pandas_gbq
33
from pathlib import Path
44
import pydata_google_auth
5+
from pydata_google_auth.exceptions import PyDataCredentialsError
56
from google.cloud import bigquery
67
from google.cloud import bigquery_storage_v1
78
from functools import partialmethod
9+
import re
810
import pandas as pd
911
from basedosdados.upload.base import Base
1012
from functools import partialmethod
11-
from basedosdados.validation.exceptions import BaseDosDadosException
13+
from basedosdados.exceptions import (
14+
BaseDosDadosException, BaseDosDadosAccessDeniedException,
15+
BaseDosDadosAuthorizationException, BaseDosDadosInvalidProjectIDException,
16+
BaseDosDadosNoBillingProjectIDException
17+
)
1218
from pandas_gbq.gbq import GenericGBQException
1319

1420

@@ -149,7 +155,6 @@ def read_sql(query, billing_project_id=None, from_file=False, reauth=False):
149155
"""
150156

151157
try:
152-
153158
# Set a two hours timeout
154159
bigquery_storage_v1.client.BigQueryReadClient.read_rows = partialmethod(
155160
bigquery_storage_v1.client.BigQueryReadClient.read_rows,
@@ -161,38 +166,26 @@ def read_sql(query, billing_project_id=None, from_file=False, reauth=False):
161166
credentials=credentials(from_file=from_file, reauth=reauth),
162167
project_id=billing_project_id,
163168
)
164-
except (OSError, ValueError) as e:
165-
msg = (
166-
"\nWe are not sure which Google Cloud project should be billed.\n"
167-
"First, you should make sure that you have a Google Cloud project.\n"
168-
"If you don't have one, set one up following these steps: \n"
169-
"\t1. Go to this link https://console.cloud.google.com/projectselector2/home/dashboard\n"
170-
"\t2. Agree with Terms of Service if asked\n"
171-
"\t3. Click in Create Project\n"
172-
"\t4. Put a cool name in your project\n"
173-
"\t5. Hit create\n"
174-
"\n"
175-
"Copy the Project ID, (notice that it is not the Project Name)\n"
176-
"Now, you have two options:\n"
177-
"1. Add an argument to your function poiting to the billing project id.\n"
178-
" Like `bd.read_table('br_ibge_pib', 'municipios', billing_project_id=<YOUR_PROJECT_ID>)`\n"
179-
"2. You can set a project_id in the environment by running the following command in your terminal: `gcloud config set project <YOUR_PROJECT_ID>`.\n"
180-
" Bear in mind that you need `gcloud` installed."
181-
)
182-
raise BaseDosDadosException(msg) from e
169+
183170
except GenericGBQException as e:
184171
if "Reason: 403" in str(e):
185-
raise BaseDosDadosException(
186-
"\nYou still don't have a Google Cloud Project.\n"
187-
"Set one up following these steps: \n"
188-
"1. Go to this link https://console.cloud.google.com/projectselector2/home/dashboard\n"
189-
"2. Agree with Terms of Service if asked\n"
190-
"3. Click in Create Project\n"
191-
"4. Put a cool name in your project\n"
192-
"5. Hit create\n"
193-
"6. Rerun this command with the flag `reauth=True`. \n"
194-
" Like `read_table('br_ibge_pib', 'municipios', reauth=True)`"
195-
)
172+
raise BaseDosDadosAccessDeniedException
173+
174+
elif re.match("Reason: 400 POST .* [Pp]roject[ ]*I[Dd]", str(e)):
175+
raise BaseDosDadosInvalidProjectIDException
176+
177+
raise
178+
179+
except PyDataCredentialsError as e:
180+
raise BaseDosDadosAuthorizationException
181+
182+
except (OSError, ValueError) as e:
183+
exc_from_no_billing_id = (
184+
"Could not determine project ID" in str(e) or \
185+
"reading from stdin while output is captured" in str(e)
186+
)
187+
if exc_from_no_billing_id:
188+
raise BaseDosDadosNoBillingProjectIDException
196189
raise
197190

198191

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
class BaseDosDadosException(Exception):
2+
"""Exclusive Exception from Base dos Dados"""
3+
4+
5+
class BaseDosDadosAccessDeniedException(BaseDosDadosException):
6+
"""Exception raised if the user provides a wrong GCP project ID."""
7+
def __init__(self):
8+
self.message = (
9+
"\nAre you sure you are using the right `billing_project_id`?"
10+
"\nYou must use the Project ID available in your Google Cloud"
11+
" console home page at https://console.cloud.google.com/home/dashboard"
12+
"\nIf you still don't have a Google Cloud Project, you have to "
13+
"create one.\n"
14+
"You can set one up by following these steps: \n"
15+
"1. Go to this link https://console.cloud.google.com/projectselector2/home/dashboard\n"
16+
"2. Agree with Terms of Service if asked\n"
17+
"3. Click in Create Project\n"
18+
"4. Put a cool name in your project\n"
19+
"5. Hit create\n"
20+
"6. Rerun this command with the flag `reauth=True`. \n"
21+
" Like `read_table('br_ibge_pib', 'municipios', "
22+
"billing_project_id=<YOUR_PROJECT_ID>, reauth=True)`"
23+
)
24+
super().__init__(self.message)
25+
26+
27+
class BaseDosDadosInvalidProjectIDException(BaseDosDadosException):
28+
"""Exception raised if the user provides an invalid GCP project ID."""
29+
def __init__(self):
30+
self.message = (
31+
"\nYou are using an invalid `billing_project_id`.\nMake sure "
32+
"you set it to the Project ID available in your Google Cloud"
33+
" console home page at https://console.cloud.google.com/home/dashboard"
34+
)
35+
super().__init__(self.message)
36+
37+
38+
class BaseDosDadosNoBillingProjectIDException(BaseDosDadosException):
39+
"""Exception raised if the user provides no GCP billing project ID."""
40+
def __init__(self):
41+
self.message = (
42+
"\nWe are not sure which Google Cloud project should be billed.\n"
43+
"First, you should make sure that you have a Google Cloud project.\n"
44+
"If you don't have one, set one up following these steps: \n"
45+
"\t1. Go to this link https://console.cloud.google.com/projectselector2/home/dashboard\n"
46+
"\t2. Agree with Terms of Service if asked\n"
47+
"\t3. Click in Create Project\n"
48+
"\t4. Put a cool name in your project\n"
49+
"\t5. Hit create\n"
50+
"\n"
51+
"Copy the Project ID, (notice that it is not the Project Name)\n"
52+
"Now, you have two options:\n"
53+
"1. Add an argument to your function poiting to the billing project id.\n"
54+
" Like `bd.read_table('br_ibge_pib', 'municipios', billing_project_id=<YOUR_PROJECT_ID>)`\n"
55+
"2. You can set a project_id in the environment by running the following command in your terminal: `gcloud config set project <YOUR_PROJECT_ID>`.\n"
56+
" Bear in mind that you need `gcloud` installed."
57+
)
58+
super().__init__(self.message)
59+
60+
61+
class BaseDosDadosAuthorizationException(BaseDosDadosException):
62+
"""Exception raised if the user doesn't complete the authorization
63+
process correctly."""
64+
def __init__(self):
65+
self.message = (
66+
"\nAre you sure you did the authorization process correctly?\n"
67+
"If you were given the option to enter an authorization code, "
68+
"please try again and make sure you are entering the right one."
69+
"\nYou can try again by rerunning this command with the flag "
70+
"`reauth=True`. \n\tLike `read_table('br_ibge_pib', 'municipios',"
71+
" billing_project_id=<YOUR_PROJECT_ID>, reauth=True)`"
72+
"\nThen you can click at the provided link and get the right "
73+
"authorization code."
74+
)
75+
super().__init__(self.message)

python-package/basedosdados/upload/table.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from basedosdados.upload.storage import Storage
1818
from basedosdados.upload.dataset import Dataset
1919
from basedosdados.upload.datatypes import Datatype
20-
from basedosdados.validation.exceptions import BaseDosDadosException
20+
from basedosdados.exceptions import BaseDosDadosException
2121

2222

2323
class Table(Base):

python-package/basedosdados/validation/exceptions.py

Lines changed: 0 additions & 2 deletions
This file was deleted.

python-package/tests/test_download.py

Lines changed: 79 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
from os import read
12
import pytest
23
from pathlib import Path
34
import pandas as pd
5+
from pandas_gbq.gbq import GenericGBQException
46
import shutil
57

68
from basedosdados import (
@@ -14,7 +16,10 @@
1416
get_table_columns,
1517
get_table_size,
1618
)
17-
from basedosdados.validation.exceptions import BaseDosDadosException
19+
from basedosdados.exceptions import (
20+
BaseDosDadosException, BaseDosDadosNoBillingProjectIDException,
21+
BaseDosDadosInvalidProjectIDException
22+
)
1823

1924

2025
TEST_PROJECT_ID = "basedosdados-dev"
@@ -35,13 +40,6 @@ def test_download_by_query():
3540

3641
assert SAVEFILE.exists()
3742

38-
# No billing
39-
with pytest.raises(BaseDosDadosException):
40-
download(
41-
SAVEFILE,
42-
query="select * from `basedosdados.br_ibge_pib.municipio` limit 10",
43-
)
44-
4543

4644
def test_download_by_table():
4745

@@ -57,15 +55,6 @@ def test_download_by_table():
5755

5856
assert SAVEFILE.exists()
5957

60-
# No billing
61-
with pytest.raises(BaseDosDadosException):
62-
download(
63-
SAVEFILE,
64-
dataset_id="br_ibge_pib",
65-
table_id="municipio",
66-
limit=10,
67-
)
68-
6958

7059
def test_download_save_to_path():
7160

@@ -119,6 +108,79 @@ def test_read_sql():
119108
)
120109

121110

111+
def test_read_sql_no_billing_project_id():
112+
113+
with pytest.raises(BaseDosDadosNoBillingProjectIDException) as excinfo:
114+
read_sql(
115+
query="select * from `basedosdados.br_ibge_pib.municipio` limit 10",
116+
)
117+
118+
assert (
119+
"We are not sure which Google Cloud project should be billed." \
120+
in str(excinfo.value)
121+
)
122+
123+
124+
def test_read_sql_invalid_billing_project_id():
125+
126+
pattern = r"You are using an invalid `billing_project_id`"
127+
128+
with pytest.raises(BaseDosDadosInvalidProjectIDException, match=pattern):
129+
read_sql(
130+
query="select * from `basedosdados.br_ibge_pib.municipio` limit 10",
131+
billing_project_id="inexistent_project_id",
132+
from_file=True,
133+
)
134+
135+
136+
def test_read_sql_inexistent_project():
137+
138+
with pytest.raises(GenericGBQException) as excinfo:
139+
read_sql(
140+
query="select * from `asedosdados.br_ibge_pib.municipio` limit 10",
141+
billing_project_id=TEST_PROJECT_ID,
142+
from_file=True
143+
)
144+
145+
assert "Reason: 404 Not found: Project" in str(excinfo.value)
146+
147+
148+
def test_read_sql_inexistent_dataset():
149+
150+
with pytest.raises(GenericGBQException) as excinfo:
151+
read_sql(
152+
query="select * from `basedosdados.br_ibge_inexistent.municipio` limit 10",
153+
billing_project_id=TEST_PROJECT_ID,
154+
from_file=True
155+
)
156+
157+
assert "Reason: 404 Not found: Dataset" in str(excinfo.value)
158+
159+
160+
def test_read_sql_inexistent_table():
161+
162+
with pytest.raises(GenericGBQException) as excinfo:
163+
read_sql(
164+
query="select * from `basedosdados.br_ibge_pib.inexistent` limit 10",
165+
billing_project_id=TEST_PROJECT_ID,
166+
from_file=True
167+
)
168+
169+
assert "Reason: 404 Not found: Table" in str(excinfo.value)
170+
171+
172+
def test_read_sql_syntax_error():
173+
174+
with pytest.raises(GenericGBQException) as excinfo:
175+
read_sql(
176+
query="invalid_statement * from `basedosdados.br_ibge_pib.municipio` limit 10",
177+
billing_project_id=TEST_PROJECT_ID,
178+
from_file=True
179+
)
180+
181+
assert "Reason: 400 Syntax error" in str(excinfo.value)
182+
183+
122184
def test_read_table():
123185

124186
assert isinstance(

python-package/tests/test_table.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from google.api_core.exceptions import NotFound
66

77
from basedosdados import Dataset, Table, Storage
8-
from basedosdados.validation.exceptions import BaseDosDadosException
8+
from basedosdados.exceptions import BaseDosDadosException
99

1010
DATASET_ID = "pytest"
1111
TABLE_ID = "pytest"

0 commit comments

Comments
 (0)