python-adblock

Updated Brave adblock library for Python
Log | Files | Refs | README

commit 9c684f12e1050d8b0dce9cb1e66eb2de0c5f3be6
parent 78f97d6b35b613b78727a94f3f6064b091665f55
Author: Árni Dagur <arni@dagur.eu>
Date:   Sat, 16 Jan 2021 19:47:05 +0000

Improve __repr__ (#23)

So we get something like:

    BlockerResult(matched=false, important=false, redirect=None, exception=None, filter=None, error=None)

rather than:

    BlockerResult(false, false, None, None, None, None)

Co-authored-by: Florian Bruhin <me@the-compiler.org>
Diffstat:
MCHANGELOG.md | 1+
Msrc/lib.rs | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Atests/test_repr.py | 37+++++++++++++++++++++++++++++++++++++
3 files changed, 109 insertions(+), 6 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md @@ -12,6 +12,7 @@ This project adheres to [Semantic Versioning](http://semver.org/) and [Keep a Ch ### Changes * Updated PyO3 to version `0.13`. +* Changed `__repr__` methods of classes to be more idiomatic. ### Fixes diff --git a/src/lib.rs b/src/lib.rs @@ -99,8 +99,13 @@ impl Into<BlockerResult> for RustBlockerResult { impl PyObjectProtocol for BlockerResult { fn __repr__(&self) -> PyResult<String> { Ok(format!( - "BlockerResult({}, {}, {:?}, {:?}, {:?}, {:?})", - self.matched, self.important, self.redirect, self.exception, self.filter, self.error + "BlockerResult(matched={}, important={}, redirect={}, exception={}, filter={}, error={})", + self.matched.diy_python_repr(), + self.important.diy_python_repr(), + self.redirect.diy_python_repr(), + self.exception.diy_python_repr(), + self.filter.diy_python_repr(), + self.error.diy_python_repr(), )) } } @@ -173,6 +178,7 @@ fn filter_format_from_string(filter_format: &str) -> PyResult<FilterFormat> { #[derive(Clone)] pub struct FilterSet { filter_set: RustFilterSet, + debug: bool, } #[pymethods] @@ -186,6 +192,7 @@ impl FilterSet { pub fn new(debug: bool) -> Self { Self { filter_set: RustFilterSet::new(debug), + debug, } } @@ -215,6 +222,14 @@ impl FilterSet { Ok(()) } } + +#[pyproto] +impl PyObjectProtocol for FilterSet { + fn __repr__(&self) -> PyResult<String> { + Ok(format!("FilterSet(debug={})", self.debug.diy_python_repr())) + } +} + /// Contains cosmetic filter information intended to be injected into a /// particular hostname. #[pyclass] @@ -260,12 +275,12 @@ impl Into<UrlSpecificResources> for RustUrlSpecificResources { impl PyObjectProtocol for UrlSpecificResources { fn __repr__(&self) -> PyResult<String> { Ok(format!( - "UrlSpecificResources<{} hide selectors, {} style selectors, {} exceptions, injected_javascript={:?}, generichide={}>", + "UrlSpecificResources<{} hide selectors, {} style selectors, {} exceptions, injected_javascript={}, generichide={}>", self.hide_selectors.len(), self.style_selectors.len(), self.exceptions.len(), - self.injected_script, - self.generichide, + self.injected_script.diy_python_repr(), + self.generichide.diy_python_repr(), )) } } @@ -293,6 +308,7 @@ impl PyObjectProtocol for UrlSpecificResources { #[text_signature = "($self, filter_set, optimize)"] pub struct Engine { engine: RustEngine, + optimize: bool, } #[pymethods] @@ -302,7 +318,7 @@ impl Engine { #[args(filter_set, optimize = true)] pub fn new(filter_set: FilterSet, optimize: bool) -> Self { let engine = RustEngine::from_filter_set(filter_set.filter_set, optimize); - Self { engine } + Self { engine, optimize } } /// Check if the given `url`—pointing to a resource of type `request_type`— @@ -523,3 +539,52 @@ impl Engine { .hidden_class_id_selectors(&classes, &ids, &exceptions)) } } + +#[pyproto] +impl PyObjectProtocol for Engine { + fn __repr__(&self) -> PyResult<String> { + Ok(format!( + "Engine<optimize={}>", + self.optimize.diy_python_repr() + )) + } +} + +/// PyO3 doesn't offer the ability to get the Python representation of a Rust +/// object, so we make our own trait. +trait DiyPythonRepr { + fn diy_python_repr(&self) -> String; +} + +impl<T> DiyPythonRepr for Option<T> +where + T: DiyPythonRepr, +{ + fn diy_python_repr(&self) -> String { + match self { + None => "None".to_owned(), + Some(x) => x.diy_python_repr(), + } + } +} + +impl DiyPythonRepr for String { + fn diy_python_repr(&self) -> String { + let mut res = format!("{:?}", self); + // This is safe to do since we know that `res` will always be of + // length >= 2. + res.replace_range(0..1, "'"); + res.replace_range(res.len() - 1..res.len(), "'"); + res + } +} + +impl DiyPythonRepr for bool { + fn diy_python_repr(&self) -> String { + if *self { + "True".to_owned() + } else { + "False".to_owned() + } + } +} diff --git a/tests/test_repr.py b/tests/test_repr.py @@ -0,0 +1,37 @@ +import adblock +import re + + +def assert_acceptable_repr(obj): + # Default repr is r"<[A-Za-z]+ object at 0x[0-9a-f]+>" + assert "object at" not in repr(obj) + assert re.match(r"[A-Z][a-zA-Z]+\(.*\)", repr(obj)) or re.match( + r"([A-Z][a-zA-Z]+)?<.*>", repr(obj) + ) + + +def test_has_nondefault_repr(): + for b in (True, False): + fs = adblock.FilterSet(debug=b) + assert_acceptable_repr(fs) + assert repr(b) in repr(fs) + + fs.add_filters(["||example.com^"]) + + e = adblock.Engine(fs) + assert_acceptable_repr(e) + + result = e.check_network_urls( + "https://example.com/picture.png", "https://example.net", "image" + ) + assert_acceptable_repr(result) + assert repr(result) == ( + "BlockerResult(matched={}, important={}, redirect={}, exception={}, filter={}, error={})".format( + repr(result.matched), + repr(result.important), + repr(result.redirect), + repr(result.exception), + repr(result.filter), + repr(result.error), + ) + )