Repository: AnthonyBloomer/daftlistings

Issue:
Facility SearchType Combos
The facility options available for filtering are heavily dependent on the `SearchType`. In my local version I have facilities filtering added and functioning but without any checking related to this. I was planning to resolve this issue before pushing it. It will probably make sense to solve this in the same way as whatever ends up being done for the PropertyType SearchType combos issue (https://github.com/AnthonyBloomer/daftlistings/issues/108#issue-849665232), although there are more differences in the available facilities across `SearchType` than there is in `PropertyType`. The facilities values by `SearchType` are as follows:

```
Buying Facilities:

"value":"alarm"
"value":"gas-fired-central-heating"
"value":"oil-fired-central-heating"
"value":"parking"
"value":"wheelchair-access"
"value":"wired-for-cable-television"

Renting Facilities:

"value":"alarm"
"value":"cable-television"
"value":"dishwasher"
"value":"garden-patio-balcony"
"value":"central-heating"
"value":"internet"
"value":"microwave"
"value":"parking"
"value":"pets-allowed"
"value":"smoking"
"value":"serviced-property"
"value":"dryer"
"value":"washing-machine"
"value":"wheelchair-access"

Share Facilities:

"value":"alarm"
"value":"cable-television"
"value":"dishwasher"
"value":"ensuite"
"value":"garden-patio-balcony"
"value":"central-heating"
"value":"internet"
"value":"microwave"
"value":"parking"
"value":"pets-allowed"
"value":"serviced-property"
"value":"smoking"
"value":"dryer"
"value":"washing-machine"
"value":"wheelchair-access"

New Homes Facilities:

None

Commerical Facilities:

"value":"alarm"
"value":"cat-5-cabling"
"value":"cat-6-data-cabling"
"value":"kitchen-area"
"value":"meeting-rooms"
"value":"reception"
"value":"parking"
"value":"phone-lines"
"value":"toilets"
```



Comments:

Patch:
diff --git a/README.md b/README.md
index 4fcc162..16ed94f 100644
--- a/README.md
+++ b/README.md
@@ -179,6 +179,29 @@ dublin_map.add_colorbar()
 dublin_map.save("ireland_rent.html")
 print("Done, please checkout the html file")
 ```
+
+Search for apartments for rent in Dublin with an alarm and parking.
+
+```python
+from daftlistings import Daft, Location, SearchType, PropertyType, Facility
+
+daft = Daft()
+daft.set_location(Location.DUBLIN)
+daft.set_search_type(SearchType.RESIDENTIAL_RENT)
+daft.set_property_type(PropertyType.APARTMENT)
+daft.set_facility(Facility.PARKING)
+daft.set_facility(Facility.ALARM)
+
+listings = daft.search()
+
+for listing in listings:
+    print(listing.title)
+    print(listing.abbreviated_price)
+    print(listing.daft_link)
+    print()
+```
+
+
 ## Contributing
 
   - Fork the project and clone locally.
diff --git a/daftlistings/daft.py b/daftlistings/daft.py
index c46baeb..e2ff99a 100644
--- a/daftlistings/daft.py
+++ b/daftlistings/daft.py
@@ -58,6 +58,16 @@ class Daft:
         self._filters.append({"name": name,
                               "values": [value]})
 
+    def _add_and_filter(self, name: str, value: str):
+        if self._andFilters:
+            for f in self._andFilters:
+                if f["name"] == name:
+                    if value not in f["values"]:
+                        f["values"].append(value)
+                    return
+        self._andFilters.append({"name": name,
+                              "values": [value]})
+
     def _add_sort_filter(self, sort_filter: str):
         self._sort_filter = sort_filter
 
@@ -153,6 +163,21 @@ class Daft:
         else:
             raise TypeError("Argument must be location.Location or string.")
 
+    def set_facility(self, facility: Facility):
+        if self._section == None:
+            raise ValueError('SearchType must be set before Facility')
+        else:
+            if isinstance(facility, Facility):
+                if self._section in [s.value for s in facility.valid_types]:
+                    self._add_and_filter('facilities', facility.value)
+                else:
+                    search_type = [(name,member) for name, member in SearchType.__members__.items() if member.value == self._section][0]
+                    compatible_facilities = [f.name for f in Facility if search_type[1] in f.valid_types]
+                    raise ValueError(f"Facility {facility.name} incompatible with SearchType {search_type[0]}\nThe following facilities are compatible with this SearchType:\n{compatible_facilities}")
+            else:
+                raise TypeError("Argument must be of type Facility")
+           
+
     def set_sort_type(self, sort_type: SortType):
         if isinstance(sort_type, SortType):
             self._add_sort_filter(sort_type.value)
@@ -178,6 +203,8 @@ class Daft:
             payload["section"] = self._section
         if self._filters:
             payload["filters"] = self._filters
+        if self._andFilters:
+            payload["andFilters"] = self._andFilters
         if self._ranges:
             payload["ranges"] = self._ranges
         if self._geoFilter:
diff --git a/daftlistings/enums.py b/daftlistings/enums.py
index b166c1a..04f2bd4 100644
--- a/daftlistings/enums.py
+++ b/daftlistings/enums.py
@@ -62,6 +62,42 @@ class MiscFilter(enum.Enum):
     TOILETS = "toilets"
 
 
+class Facility(enum.Enum):
+    def __new__(cls, *args, **kwargs):
+        obj = object.__new__(cls)
+        obj._value_ = args[0]
+        return obj
+
+    def __init__(self, _, valid_types):
+        self.valid_types = valid_types
+
+    ALARM = ("alarm", [SearchType.RESIDENTIAL_SALE, SearchType.RESIDENTIAL_RENT, SearchType.COMMERCIAL_SALE, SearchType.COMMERCIAL_RENT, SearchType.SHARING, SearchType.STUDENT_ACCOMMODATION])
+    CENTRAL_HEATING_GAS = ("gas-fired-central-heating", [SearchType.RESIDENTIAL_SALE])
+    CENTRAL_HEATING_OIL = ("oil-fired-central-heating", [SearchType.RESIDENTIAL_SALE])
+    PARKING = ("parking", [SearchType.RESIDENTIAL_SALE, SearchType.RESIDENTIAL_RENT, SearchType.COMMERCIAL_SALE, SearchType.COMMERCIAL_RENT, SearchType.SHARING, SearchType.STUDENT_ACCOMMODATION])
+    WHEELCHAIR_ACCESS = ("wheelchair-access", [SearchType.RESIDENTIAL_SALE, SearchType.RESIDENTIAL_RENT, SearchType.SHARING, SearchType.STUDENT_ACCOMMODATION])
+    WIRED_FOR_CABLE_TELEVISION = ("wired-for-cable-television", [SearchType.RESIDENTIAL_SALE])
+    CABLE_TELEVISION = ("cable-television", [SearchType.RESIDENTIAL_RENT, SearchType.SHARING, SearchType.STUDENT_ACCOMMODATION])    
+    DISHWASHER = ("dishwasher", [SearchType.RESIDENTIAL_RENT, SearchType.SHARING, SearchType.STUDENT_ACCOMMODATION])
+    GARDEN_PATIO_BALCONY = ("garden-patio-balcony", [SearchType.RESIDENTIAL_RENT, SearchType.SHARING, SearchType.STUDENT_ACCOMMODATION])
+    CENTRAL_HEATING = ("central-heating", [SearchType.RESIDENTIAL_RENT, SearchType.SHARING, SearchType.STUDENT_ACCOMMODATION])
+    INTERNET = ("internet", [SearchType.RESIDENTIAL_RENT, SearchType.SHARING, SearchType.STUDENT_ACCOMMODATION])
+    MICROWAVE = ("microwave", [SearchType.RESIDENTIAL_RENT, SearchType.SHARING, SearchType.STUDENT_ACCOMMODATION])
+    PETS_ALLOWED = ("pets-allowed", [SearchType.RESIDENTIAL_RENT, SearchType.SHARING, SearchType.STUDENT_ACCOMMODATION])
+    SMOKING = ("smoking", [SearchType.RESIDENTIAL_RENT, SearchType.SHARING, SearchType.STUDENT_ACCOMMODATION])
+    SERVICED_PROPERTY = ("serviced-property", [SearchType.RESIDENTIAL_RENT, SearchType.SHARING, SearchType.STUDENT_ACCOMMODATION])
+    DRYER = ("dryer", [SearchType.RESIDENTIAL_RENT, SearchType.SHARING, SearchType.STUDENT_ACCOMMODATION])
+    WASHING_MACHINE = ("washing-machine", [SearchType.RESIDENTIAL_RENT, SearchType.SHARING, SearchType.STUDENT_ACCOMMODATION])
+    ENSUITE = ("ensuite", [SearchType.SHARING, SearchType.STUDENT_ACCOMMODATION])
+    CAT_5_CABLING = ("cat-5-cabling", [SearchType.COMMERCIAL_SALE, SearchType.COMMERCIAL_RENT])
+    CAT_6_CABLING = ("cat-6-data-cabling", [SearchType.COMMERCIAL_SALE, SearchType.COMMERCIAL_RENT])
+    KITCHEN_AREA = ("kitchen-area", [SearchType.COMMERCIAL_SALE, SearchType.COMMERCIAL_RENT])
+    MEETING_ROOMS = ("meeting-rooms", [SearchType.COMMERCIAL_SALE, SearchType.COMMERCIAL_RENT])
+    RECEPTION = ("reception", [SearchType.COMMERCIAL_SALE, SearchType.COMMERCIAL_RENT])
+    PHONE_LINES = ("phone-lines", [SearchType.COMMERCIAL_SALE, SearchType.COMMERCIAL_RENT])
+    TOILETS = ("toilets", [SearchType.COMMERCIAL_SALE, SearchType.COMMERCIAL_RENT])
+
+
 class AddedSince(enum.Enum):
     DAYS_3 = "now-3d/d"
     DAYS_7 = "now-7d/d"
diff --git a/examples/facilities.py b/examples/facilities.py
new file mode 100644
index 0000000..e864f83
--- /dev/null
+++ b/examples/facilities.py
@@ -0,0 +1,16 @@
+from daftlistings import Daft, Location, SearchType, PropertyType, Facility
+
+daft = Daft()
+daft.set_location(Location.DUBLIN)
+daft.set_search_type(SearchType.RESIDENTIAL_RENT)
+daft.set_property_type(PropertyType.APARTMENT)
+daft.set_facility(Facility.PARKING)
+daft.set_facility(Facility.ALARM)
+
+listings = daft.search()
+
+for listing in listings:
+    print(listing.title)
+    print(listing.abbreviated_price)
+    print(listing.daft_link)
+    print()
\ No newline at end of file


Test patch:
diff --git a/tests/test_daft_search.py b/tests/test_daft_search.py
index 9942827..f14bbbb 100644
--- a/tests/test_daft_search.py
+++ b/tests/test_daft_search.py
@@ -10,6 +10,7 @@ from daftlistings import (
     Listing,
     AddedSince,
     PropertyType,
+    Facility
 )
 
 
@@ -18,7 +19,8 @@ class DaftTest(unittest.TestCase):
     def test_search(self, mock_post):
         url = "https://search-gateway.dsch.ie/v1/listings"
         payload = {
-            "section": "new-homes",
+            "section": "residential-for-sale",
+            "andFilters": [{"name":"facilities", "values": ["alarm"]}],
             "ranges": [
                 {"name": "salePrice", "from": "250000", "to": "300000"},
                 {"name": "numBeds", "from": "3", "to": "3"},
@@ -38,7 +40,7 @@ class DaftTest(unittest.TestCase):
 
         daft = Daft()
 
-        daft.set_search_type(SearchType.NEW_HOMES)
+        daft.set_search_type(SearchType.RESIDENTIAL_SALE)
         daft.set_location(Location.KILDARE)
         daft.set_location("Kildare")
         daft.set_sort_type(SortType.PRICE_ASC)
@@ -51,6 +53,7 @@ class DaftTest(unittest.TestCase):
         daft.set_max_floor_size(1000)
         daft.set_min_floor_size(1000)
         daft.set_added_since(AddedSince.DAYS_14)
+        daft.set_facility(Facility.ALARM)
         daft.search()
 
         mock_post.assert_called_with(url, headers=headers, json=payload)