Skip to content

Commit

Permalink
Introduce use of app-specific exceptions
Browse files Browse the repository at this point in the history
  • Loading branch information
mrchrisadams committed Nov 13, 2024
1 parent dd83624 commit 3b4d442
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 26 deletions.
51 changes: 32 additions & 19 deletions src/carbon_txt/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from django.core.management import execute_from_command_line

from . import finders, parsers_toml
from .exceptions import InsecureKeyException
from . import exceptions
from .schemas import CarbonTxtFile

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -51,7 +51,7 @@ def validate_domain(domain: str):
rich.print(f"Carbon.txt file found at {result}.\n")
else:
rich.print(f"No valid carbon.txt file found on {domain}.\n")
return 1
typer.Exit(code=1)

# fetch and parse file
content = parser.get_carbon_txt_file(result)
Expand All @@ -62,11 +62,11 @@ def validate_domain(domain: str):
if isinstance(validation_results, CarbonTxtFile):
_log_validation_results(success=True)
_log_validated_carbon_txt_object(validation_results)
return 0
typer.Exit(code=0)
else:
_log_validation_results(success=False)
_log_validated_carbon_txt_object(validation_results)
return 1
typer.Exit(code=1)


@validate_app.command("file")
Expand All @@ -80,30 +80,43 @@ def validate_file(
else:
try:
result = file_finder.resolve_uri(file_path)
logger.info("Carbon.txt file found at {result}.\n")
content = parser.get_carbon_txt_file(result)
logger.info("Carbon.txt file parsed .\n")
parsed_result = parser.parse_toml(content)

# the file path is local, but we can't access it
except FileNotFoundError:
full_file_path = Path(file_path).absolute()
rich.print(f"No valid carbon.txt file found at {full_file_path}. \n")
return 1
raise typer.Exit(code=1)

if result:
rich.print(f"Carbon.txt file found at {result}.\n")
content = parser.get_carbon_txt_file(result)
else:
full_file_path = Path(file_path).absolute()
rich.print(f"No valid carbon.txt file found at {full_file_path}. \n")
return 1
# the file path is remote, and we can't access it
except exceptions.UnreachableCarbonTxtFile as e:
logger.error(f"Error: {e}")
raise typer.Exit(code=1)

# the file path is reachable, and but it's not valid TOML
except exceptions.NotParseableTOML as e:
rich.print(
f"A carbon.txt file was found at {file_path}: but it wasn't parseable TOML. Error was: {e}"
)
raise typer.Exit(code=1)

except Exception as e:
rich.print(f"An unexpected error occurred: {e}")
raise typer.Exit(code=1)

parsed_result = parser.parse_toml(content)
validation_results = parser.validate_as_carbon_txt(parsed_result)

if isinstance(validation_results, CarbonTxtFile):
_log_validation_results(success=True)
_log_validated_carbon_txt_object(validation_results)
return 0
return typer.Exit(code=0)
else:
_log_validation_results(success=False)
_log_validated_carbon_txt_object(validation_results)
return 1
return typer.Exit(code=0)


@app.command()
Expand All @@ -119,7 +132,7 @@ def schema():
rich.print(json.dumps(schema, indent=2))
else:
print(json.dumps(schema, indent=2))
return 0
typer.Exit(code=0)


def configure_django(
Expand Down Expand Up @@ -173,12 +186,12 @@ def serve(
os.system("granian --interface wsgi carbon_txt.web.config.wsgi:application")
else:
execute_from_command_line(["manage.py", "runserver", f"{host}:{port}"])
except InsecureKeyException as e:
except exceptions.InsecureKeyException as e:
rich.print(f"{e}")
rich.print(
"For more, see the docs at https://carbon-txt-validator.readthedocs.io/en/latest/deployment.html"
)
sys.exit(1)
typer.Exit(code=1)
# anything unexpected we provide a clear path to raising an issue to fix it
except Exception as e:
rich.print(f"An error occurred: {e}")
Expand All @@ -189,7 +202,7 @@ def serve(
"with steps to reproduce this error"
)
)
sys.exit(1)
typer.Exit(code=1)


if __name__ == "__main__":
Expand Down
22 changes: 21 additions & 1 deletion src/carbon_txt/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,24 @@
class InsecureKeyException(Exception):
"""Raised when the default SECRET_KEY is used in a production django server environment"""
"""
Raised when the default SECRET_KEY is used in a
production django server environment
"""

pass


class UnreachableCarbonTxtFile(Exception):
"""
Raised when we can't reach the carbon.txt file
"""

pass


class NotParseableTOML(Exception):
"""
Raised when we have a response at a the given carbon txt
URL, but it is not parsable TOML file
"""

pass
11 changes: 9 additions & 2 deletions src/carbon_txt/finders.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import logging
import rich # noqa

from .exceptions import UnreachableCarbonTxtFile

logger = logging.getLogger(__name__)

logger.setLevel(logging.DEBUG)
Expand Down Expand Up @@ -129,8 +131,13 @@ def resolve_uri(self, uri: str) -> str:
return str(path_to_file.resolve())

# if the URI is a valid HTTP or HTTPS URI, we check if the URI is reachable
response = httpx.head(parsed_uri.geturl())

# and if there is a 'via' header in the response, we follow that
try:
response = httpx.head(parsed_uri.geturl())
except httpx._exceptions.ConnectError:
raise UnreachableCarbonTxtFile(
f"Could not connect to {parsed_uri.geturl()}."
)
# catch any errors from the request
response.raise_for_status()

Expand Down
24 changes: 20 additions & 4 deletions src/carbon_txt/parsers_toml.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import httpx
import pathlib

from . import exceptions


import logging

Expand Down Expand Up @@ -30,13 +32,27 @@ def get_carbon_txt_file(self, str) -> str:
if pathlib.Path(str).exists():
return pathlib.Path(str).read_text()

def fetch_pared_carbon_txt_file(self, uri: str) -> dict:
"""
Accept a URI and return a parsed TOML object.
"""
try:
carbon_txt = self.get_carbon_txt_file(uri)
parsed = self.parse_toml(carbon_txt)
return parsed
except toml.TOMLDecodeError as e:
raise exceptions.NotParseableTOML(e)

def parse_toml(self, str) -> dict:
"""
Accept a string of TOML and return a CarbonTxtFile
object
Accept a string of TOML and return a dict representing the
keys and values going into a CarbonTxtFile object.
"""
parsed = toml.loads(str)
return parsed
try:
parsed = toml.loads(str)
return parsed
except toml.TOMLDecodeError as e:
raise exceptions.NotParseableTOML(e)

def validate_as_carbon_txt(self, parsed) -> schemas.CarbonTxtFile:
"""
Expand Down
11 changes: 11 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@ def test_lookup_file(self):
assert result.exit_code == 0
assert "https://used-in-tests.carbontxt.org" in result.stdout

def test_lookup_missing_file(self):
"""
Run our CLI to `carbontxt validate file https://some-domain.com/carbon.txt`,
"""

# Update to a domain we know will not ever have a carbon.txt file
result = runner.invoke(
app, ["validate", "file", "https://www.thegreenwebfoundation.org"]
)
assert result.exit_code == 1

def test_schema(self):
"""
Run our CLI to `carbontxt schema`, and confirm we
Expand Down

0 comments on commit 3b4d442

Please sign in to comment.