ownerrez-wrapper 0.1.0__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Geody Moore
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,5 @@
1
+ include LICENSE
2
+ include README.md
3
+ include requirements.txt
4
+ include requirements-dev.txt
5
+ recursive-include tests *.py
@@ -0,0 +1,187 @@
1
+ Metadata-Version: 2.2
2
+ Name: ownerrez-wrapper
3
+ Version: 0.1.0
4
+ Summary: A Python wrapper for the OwnerRez API
5
+ Home-page: https://github.com/gmoorevt/ownerrez-wrapper
6
+ Author: Geody Moore
7
+ Author-email: geody.moore@gmail.com
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: requests>=2.25.0
20
+ Requires-Dist: python-dateutil>=2.8.0
21
+ Requires-Dist: python-dotenv>=1.0.0
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest>=6.0.0; extra == "dev"
24
+ Requires-Dist: pytest-cov>=2.0.0; extra == "dev"
25
+ Requires-Dist: black>=22.0.0; extra == "dev"
26
+ Requires-Dist: isort>=5.0.0; extra == "dev"
27
+ Requires-Dist: flake8>=3.9.0; extra == "dev"
28
+ Requires-Dist: mypy>=0.900; extra == "dev"
29
+ Requires-Dist: freezegun>=1.2.0; extra == "dev"
30
+ Dynamic: author
31
+ Dynamic: author-email
32
+ Dynamic: classifier
33
+ Dynamic: description
34
+ Dynamic: description-content-type
35
+ Dynamic: home-page
36
+ Dynamic: provides-extra
37
+ Dynamic: requires-dist
38
+ Dynamic: requires-python
39
+ Dynamic: summary
40
+
41
+ # OwnerRez API Wrapper
42
+
43
+ A Python wrapper for the OwnerRez API. This package provides a simple and intuitive interface to interact with the OwnerRez API v2.
44
+
45
+ ## Requirements
46
+
47
+ - Python 3.10 or higher
48
+
49
+ ## Installation
50
+
51
+ ```bash
52
+ pip install ownerrez-wrapper
53
+ ```
54
+
55
+ ## Python Usage
56
+
57
+ ```python
58
+ from ownerrez_wrapper import API
59
+ from datetime import datetime
60
+
61
+ # Initialize the API client
62
+ api = API(username="your_username", token="your_token")
63
+
64
+ # Get all properties
65
+ properties = api.getproperties()
66
+ for prop in properties:
67
+ print(f"Property: {prop.name} (ID: {prop.id})")
68
+
69
+ # Get bookings for a property
70
+ bookings = api.getbookings(
71
+ property_id=123,
72
+ since_utc=datetime(2024, 1, 1)
73
+ )
74
+ for booking in bookings:
75
+ print(f"Booking: {booking.arrival} to {booking.departure}")
76
+
77
+ # Get a specific booking
78
+ booking = api.getbooking(booking_id=456)
79
+ print(f"Guest ID: {booking.guest_id}")
80
+
81
+ # Get guest details
82
+ guest = api.getguest(guest_id=789)
83
+ print(f"Guest: {guest.first_name} {guest.last_name}")
84
+
85
+ # Check if a property is currently booked
86
+ is_booked = api.isunitbooked(property_id=123)
87
+ print(f"Property is booked: {is_booked}")
88
+ ```
89
+
90
+ ## API Documentation
91
+
92
+ ### API Class Methods
93
+
94
+ - `getproperties()` -> List[Property]
95
+ - Returns a list of all properties
96
+
97
+ - `getproperty(property_id: int)` -> Property
98
+ - Get details for a specific property
99
+ - Parameters:
100
+ - property_id: The property ID
101
+
102
+ - `getbookings(property_id: int, since_utc: datetime)` -> List[Booking]
103
+ - Get bookings for a specific property since a given date
104
+ - Parameters:
105
+ - property_id: The property ID
106
+ - since_utc: DateTime object for the start date
107
+
108
+ - `getbooking(booking_id: int)` -> Booking
109
+ - Get details for a specific booking
110
+ - Parameters:
111
+ - booking_id: The booking ID
112
+
113
+ - `getguest(guest_id: int)` -> Guest
114
+ - Get details for a specific guest
115
+ - Parameters:
116
+ - guest_id: The guest ID
117
+
118
+ - `isunitbooked(property_id: int)` -> bool
119
+ - Check if a property is currently booked
120
+ - Parameters:
121
+ - property_id: The property ID
122
+
123
+ ### Data Models
124
+
125
+ The API returns strongly-typed objects that provide a rich interface to the OwnerRez data. For detailed documentation of all data models, including all available fields and usage examples, see [Data Models Documentation](docs/models.md).
126
+
127
+ Main models:
128
+ - `Property`: Contains property details (id, name, bedrooms, etc.)
129
+ - `Booking`: Contains booking information (arrival, departure, guest, etc.)
130
+ - `Guest`: Contains guest information (name, contact details, etc.)
131
+
132
+ Supporting models:
133
+ - `Address`: Physical address information
134
+ - `EmailAddress`: Email contact information
135
+ - `Phone`: Phone contact information
136
+ - `Charge`: Booking charge information
137
+ - `DoorCode`: Property access codes
138
+
139
+ ## CLI Tool (for testing)
140
+
141
+ A command-line interface is included for testing and debugging. You can provide credentials via environment variables or a `.env` file:
142
+
143
+ ```bash
144
+ # Using .env file
145
+ echo "OWNERREZ_USERNAME=your_username" > .env
146
+ echo "OWNERREZ_TOKEN=your_token" >> .env
147
+
148
+ # List properties
149
+ ownerrez properties
150
+
151
+ # Get property details
152
+ ownerrez property 123
153
+
154
+ # Check bookings
155
+ ownerrez bookings 123 --since 2024-01-01
156
+
157
+ # Get booking details
158
+ ownerrez booking 456
159
+ ```
160
+
161
+ ## Development
162
+
163
+ To set up the development environment:
164
+
165
+ ```bash
166
+ git clone https://github.com/gmoorevt/ownerrez-wrapper.git
167
+ cd ownerrez-wrapper
168
+ pip install -e ".[dev]"
169
+ ```
170
+
171
+ To run tests:
172
+
173
+ ```bash
174
+ pytest
175
+ ```
176
+
177
+ ## License
178
+
179
+ This project is licensed under the MIT License - see the LICENSE file for details.
180
+
181
+ ## Author
182
+
183
+ - Geody Moore (geody.moore@gmail.com)
184
+
185
+ ## Contributing
186
+
187
+ Contributions are welcome! Please feel free to submit a Pull Request.
@@ -0,0 +1,147 @@
1
+ # OwnerRez API Wrapper
2
+
3
+ A Python wrapper for the OwnerRez API. This package provides a simple and intuitive interface to interact with the OwnerRez API v2.
4
+
5
+ ## Requirements
6
+
7
+ - Python 3.10 or higher
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ pip install ownerrez-wrapper
13
+ ```
14
+
15
+ ## Python Usage
16
+
17
+ ```python
18
+ from ownerrez_wrapper import API
19
+ from datetime import datetime
20
+
21
+ # Initialize the API client
22
+ api = API(username="your_username", token="your_token")
23
+
24
+ # Get all properties
25
+ properties = api.getproperties()
26
+ for prop in properties:
27
+ print(f"Property: {prop.name} (ID: {prop.id})")
28
+
29
+ # Get bookings for a property
30
+ bookings = api.getbookings(
31
+ property_id=123,
32
+ since_utc=datetime(2024, 1, 1)
33
+ )
34
+ for booking in bookings:
35
+ print(f"Booking: {booking.arrival} to {booking.departure}")
36
+
37
+ # Get a specific booking
38
+ booking = api.getbooking(booking_id=456)
39
+ print(f"Guest ID: {booking.guest_id}")
40
+
41
+ # Get guest details
42
+ guest = api.getguest(guest_id=789)
43
+ print(f"Guest: {guest.first_name} {guest.last_name}")
44
+
45
+ # Check if a property is currently booked
46
+ is_booked = api.isunitbooked(property_id=123)
47
+ print(f"Property is booked: {is_booked}")
48
+ ```
49
+
50
+ ## API Documentation
51
+
52
+ ### API Class Methods
53
+
54
+ - `getproperties()` -> List[Property]
55
+ - Returns a list of all properties
56
+
57
+ - `getproperty(property_id: int)` -> Property
58
+ - Get details for a specific property
59
+ - Parameters:
60
+ - property_id: The property ID
61
+
62
+ - `getbookings(property_id: int, since_utc: datetime)` -> List[Booking]
63
+ - Get bookings for a specific property since a given date
64
+ - Parameters:
65
+ - property_id: The property ID
66
+ - since_utc: DateTime object for the start date
67
+
68
+ - `getbooking(booking_id: int)` -> Booking
69
+ - Get details for a specific booking
70
+ - Parameters:
71
+ - booking_id: The booking ID
72
+
73
+ - `getguest(guest_id: int)` -> Guest
74
+ - Get details for a specific guest
75
+ - Parameters:
76
+ - guest_id: The guest ID
77
+
78
+ - `isunitbooked(property_id: int)` -> bool
79
+ - Check if a property is currently booked
80
+ - Parameters:
81
+ - property_id: The property ID
82
+
83
+ ### Data Models
84
+
85
+ The API returns strongly-typed objects that provide a rich interface to the OwnerRez data. For detailed documentation of all data models, including all available fields and usage examples, see [Data Models Documentation](docs/models.md).
86
+
87
+ Main models:
88
+ - `Property`: Contains property details (id, name, bedrooms, etc.)
89
+ - `Booking`: Contains booking information (arrival, departure, guest, etc.)
90
+ - `Guest`: Contains guest information (name, contact details, etc.)
91
+
92
+ Supporting models:
93
+ - `Address`: Physical address information
94
+ - `EmailAddress`: Email contact information
95
+ - `Phone`: Phone contact information
96
+ - `Charge`: Booking charge information
97
+ - `DoorCode`: Property access codes
98
+
99
+ ## CLI Tool (for testing)
100
+
101
+ A command-line interface is included for testing and debugging. You can provide credentials via environment variables or a `.env` file:
102
+
103
+ ```bash
104
+ # Using .env file
105
+ echo "OWNERREZ_USERNAME=your_username" > .env
106
+ echo "OWNERREZ_TOKEN=your_token" >> .env
107
+
108
+ # List properties
109
+ ownerrez properties
110
+
111
+ # Get property details
112
+ ownerrez property 123
113
+
114
+ # Check bookings
115
+ ownerrez bookings 123 --since 2024-01-01
116
+
117
+ # Get booking details
118
+ ownerrez booking 456
119
+ ```
120
+
121
+ ## Development
122
+
123
+ To set up the development environment:
124
+
125
+ ```bash
126
+ git clone https://github.com/gmoorevt/ownerrez-wrapper.git
127
+ cd ownerrez-wrapper
128
+ pip install -e ".[dev]"
129
+ ```
130
+
131
+ To run tests:
132
+
133
+ ```bash
134
+ pytest
135
+ ```
136
+
137
+ ## License
138
+
139
+ This project is licensed under the MIT License - see the LICENSE file for details.
140
+
141
+ ## Author
142
+
143
+ - Geody Moore (geody.moore@gmail.com)
144
+
145
+ ## Contributing
146
+
147
+ Contributions are welcome! Please feel free to submit a Pull Request.
@@ -0,0 +1,15 @@
1
+ """
2
+ OwnerRez API Wrapper
3
+ ~~~~~~~~~~~~~~~~~~~
4
+
5
+ A Python wrapper for the OwnerRez API.
6
+ """
7
+
8
+ from .api import API
9
+ from .cli import main as cli
10
+
11
+ __version__ = "0.1.0"
12
+ __author__ = "Geody Moore"
13
+ __email__ = "geody.moore@gmail.com"
14
+
15
+ __all__ = ["API", "cli"]
@@ -0,0 +1,84 @@
1
+ from typing import List
2
+ from datetime import datetime
3
+ from .restAdapter import RestAdapter
4
+ from .model import Property, Booking, Guest
5
+
6
+
7
+ class API(object):
8
+ """
9
+ OwnerRez API wrapper class
10
+ """
11
+
12
+ def __init__(self,username, token):
13
+ """
14
+ Initialize the OwnerRez API wrapper with the OwnerRez username and token
15
+ :param username: OwnerRez username
16
+ :param token: OwnerRez token
17
+ """
18
+ self.username = username
19
+ self.token = token
20
+
21
+
22
+ def getproperties(self) -> list:
23
+ """
24
+ Get a list of properties.
25
+ """
26
+ restAdapt = RestAdapter(self.username,self.token)
27
+ results = []
28
+ property_list = restAdapt.get(endpoint='properties')
29
+ for prop in property_list.data['items']:
30
+ prop = Property(**prop)
31
+ results.append(prop)
32
+ return results
33
+
34
+ def getproperty(self, property_id: int) -> Property:
35
+ """
36
+ Get a single property by ID.
37
+ """
38
+ restAdapt = RestAdapter(self.username,self.token)
39
+ property_data = restAdapt.get(endpoint=f'properties/{property_id}')
40
+ return Property(**property_data.data)
41
+
42
+ def getbookings(self, property_id: int, since_utc: datetime) -> List[Booking]:
43
+ """
44
+ Get a list of bookings for a property since a given date.
45
+ """
46
+ restAdapt = RestAdapter(self.username,self.token)
47
+ results = []
48
+ params = {'since_utc': since_utc, 'property_id': property_id}
49
+ booking_list = restAdapt.get(endpoint='bookings', ep_params=params)
50
+
51
+ for booking in booking_list.data['items']:
52
+ booking = Booking(**booking)
53
+ results.append(booking)
54
+ return results
55
+
56
+ def getbooking(self, booking_id: int) -> Booking:
57
+ """
58
+ Get a single booking by ID.
59
+ """
60
+ restAdapt = RestAdapter(self.username,self.token)
61
+ booking = restAdapt.get(endpoint=f'bookings/{booking_id}')
62
+ return Booking(**booking.data)
63
+
64
+ def getguest(self, guest_id: int) -> Guest:
65
+ """
66
+ Get a single guest by ID.
67
+ """
68
+ restAdapt = RestAdapter(self.username,self.token)
69
+ guest = restAdapt.get(endpoint=f'guests/{guest_id}')
70
+ return Guest(**guest.data)
71
+
72
+ def isunitbooked(self, property_id: int) -> bool:
73
+ """
74
+ Check if a unit is booked today.
75
+ """
76
+ today = datetime.today()
77
+ bookings = self.getbookings(property_id=property_id, since_utc=today)
78
+ for booking in bookings:
79
+ # Convert string dates to datetime if they aren't already
80
+ arrival = booking.arrival if isinstance(booking.arrival, datetime) else datetime.strptime(booking.arrival, "%Y-%m-%d")
81
+ departure = booking.departure if isinstance(booking.departure, datetime) else datetime.strptime(booking.departure, "%Y-%m-%d")
82
+ if arrival <= today and departure >= today:
83
+ return True
84
+ return False
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env python3
2
+ import argparse
3
+ from datetime import datetime
4
+ import json
5
+ from typing import Any
6
+ import os
7
+ from pathlib import Path
8
+ from dotenv import load_dotenv
9
+ from .api import API
10
+ from uuid import UUID
11
+
12
+ def json_serial(obj: Any) -> str:
13
+ """JSON serializer for objects not serializable by default json code"""
14
+ if isinstance(obj, datetime):
15
+ return obj.isoformat()
16
+ if isinstance(obj, UUID):
17
+ return str(obj)
18
+ if hasattr(obj, '__dict__'):
19
+ return {k: v for k, v in obj.__dict__.items() if v is not None}
20
+ raise TypeError(f"Type {type(obj)} not serializable")
21
+
22
+ def print_json(data: Any) -> None:
23
+ """Print data as formatted JSON"""
24
+ print(json.dumps(data, indent=2, default=json_serial))
25
+
26
+ def load_env():
27
+ """Load environment variables from .env file"""
28
+ env_path = Path('.env')
29
+ if env_path.exists():
30
+ load_dotenv(env_path)
31
+
32
+ def main():
33
+ # Load environment variables from .env file
34
+ load_env()
35
+
36
+ parser = argparse.ArgumentParser(description='OwnerRez API CLI')
37
+
38
+ # Authentication arguments
39
+ parser.add_argument('--username', help='OwnerRez API username')
40
+ parser.add_argument('--token', help='OwnerRez API token')
41
+
42
+ # Commands
43
+ subparsers = parser.add_subparsers(dest='command', help='Commands')
44
+
45
+ # Properties command
46
+ properties_parser = subparsers.add_parser('properties', help='List all properties')
47
+
48
+ # Property command
49
+ property_parser = subparsers.add_parser('property', help='Get a specific property')
50
+ property_parser.add_argument('property_id', type=int, help='Property ID')
51
+
52
+ # Bookings command
53
+ bookings_parser = subparsers.add_parser('bookings', help='Get bookings for a property')
54
+ bookings_parser.add_argument('property_id', type=int, help='Property ID')
55
+ bookings_parser.add_argument('--since', type=str, help='Get bookings since date (YYYY-MM-DD)')
56
+
57
+ # Booking command
58
+ booking_parser = subparsers.add_parser('booking', help='Get a specific booking')
59
+ booking_parser.add_argument('booking_id', type=int, help='Booking ID')
60
+
61
+ # Guest command
62
+ guest_parser = subparsers.add_parser('guest', help='Get a specific guest')
63
+ guest_parser.add_argument('guest_id', type=int, help='Guest ID')
64
+
65
+ # Check if unit is booked command
66
+ booked_parser = subparsers.add_parser('is-booked', help='Check if a unit is currently booked')
67
+ booked_parser.add_argument('property_id', type=int, help='Property ID')
68
+
69
+ args = parser.parse_args()
70
+
71
+ # Get credentials from arguments or environment
72
+ username = args.username or os.environ.get('OWNERREZ_USERNAME')
73
+ token = args.token or os.environ.get('OWNERREZ_TOKEN')
74
+
75
+ if not username or not token:
76
+ parser.error('Username and token must be provided via arguments, environment variables, or .env file')
77
+
78
+ api = API(username=username, token=token)
79
+
80
+ try:
81
+ if args.command == 'properties':
82
+ properties = api.getproperties()
83
+ print_json(properties)
84
+
85
+ elif args.command == 'property':
86
+ property = api.getproperty(property_id=args.property_id)
87
+ print_json(property)
88
+
89
+ elif args.command == 'bookings':
90
+ since_date = datetime.strptime(args.since, "%Y-%m-%d") if args.since else datetime.today()
91
+ bookings = api.getbookings(property_id=args.property_id, since_utc=since_date)
92
+ print_json(bookings)
93
+
94
+ elif args.command == 'booking':
95
+ booking = api.getbooking(booking_id=args.booking_id)
96
+ print_json(booking)
97
+
98
+ elif args.command == 'guest':
99
+ guest = api.getguest(guest_id=args.guest_id)
100
+ print_json(guest)
101
+
102
+ elif args.command == 'is-booked':
103
+ is_booked = api.isunitbooked(property_id=args.property_id)
104
+ print_json({"is_booked": is_booked})
105
+
106
+ else:
107
+ parser.print_help()
108
+
109
+ except Exception as e:
110
+ print(f"Error: {str(e)}")
111
+ exit(1)
112
+
113
+ if __name__ == '__main__':
114
+ main()
@@ -0,0 +1 @@
1
+ BASEURL = "https://api.ownerreservations.com/v2"
@@ -0,0 +1,3 @@
1
+ class OwnerrezApiException(Exception):
2
+ """Base exception for OwnerRez API errors"""
3
+ pass
@@ -0,0 +1,218 @@
1
+ from dataclasses import dataclass
2
+ from uuid import UUID
3
+ from datetime import datetime
4
+ from typing import List, Dict, Optional, Any
5
+
6
+
7
+ @dataclass
8
+ class Result:
9
+ message: str
10
+ status: str
11
+ data: Optional[List[Dict]] = None
12
+
13
+ def __init__(self, **kwargs):
14
+ for key, value in kwargs.items():
15
+ if hasattr(self, key):
16
+ setattr(self, key, value)
17
+
18
+ @dataclass
19
+ class Address:
20
+ city: Optional[str] = None
21
+ country: Optional[str] = None
22
+ id: Optional[int] = None
23
+ is_default: Optional[bool] = None
24
+ postal_code: Optional[str] = None
25
+ province: Optional[str] = None
26
+ state: Optional[str] = None
27
+ street1: Optional[str] = None
28
+ street2: Optional[str] = None
29
+ type: Optional[str] = None
30
+
31
+ def __init__(self, **kwargs):
32
+ for key, value in kwargs.items():
33
+ if hasattr(self, key):
34
+ setattr(self, key, value)
35
+
36
+ @dataclass
37
+ class EmailAddress:
38
+ address: Optional[str] = None
39
+ id: Optional[int] = None
40
+ is_default: Optional[bool] = None
41
+ type: Optional[str] = None
42
+
43
+ def __init__(self, **kwargs):
44
+ for key, value in kwargs.items():
45
+ if hasattr(self, key):
46
+ setattr(self, key, value)
47
+
48
+ @dataclass
49
+ class Phone:
50
+ extension: Optional[str] = None
51
+ id: Optional[int] = None
52
+ is_default: Optional[bool] = None
53
+ number: Optional[str] = None
54
+ type: Optional[str] = None
55
+
56
+ def __init__(self, **kwargs):
57
+ for key, value in kwargs.items():
58
+ if hasattr(self, key):
59
+ setattr(self, key, value)
60
+
61
+ @dataclass
62
+ class Guest:
63
+ addresses: Optional[List[Address]] = None
64
+ email_addresses: Optional[List[EmailAddress]] = None
65
+ first_name: Optional[str] = None
66
+ id: Optional[int] = None
67
+ last_name: Optional[str] = None
68
+ notes: Optional[str] = None
69
+ phones: Optional[List[Phone]] = None
70
+
71
+ def __init__(self, **kwargs):
72
+ for key, value in kwargs.items():
73
+ if hasattr(self, key):
74
+ if key == 'addresses' and value:
75
+ self.addresses = [Address(**addr) for addr in value]
76
+ elif key == 'email_addresses' and value:
77
+ self.email_addresses = [EmailAddress(**email) for email in value]
78
+ elif key == 'phones' and value:
79
+ self.phones = [Phone(**phone) for phone in value]
80
+ else:
81
+ setattr(self, key, value)
82
+
83
+ @dataclass
84
+ class Property:
85
+ active: Optional[bool] = None
86
+ address: Optional[Address] = None
87
+ bathrooms: Optional[int] = None
88
+ bathrooms_full: Optional[int] = None
89
+ bathrooms_half: Optional[int] = None
90
+ bedrooms: Optional[int] = None
91
+ check_in: Optional[str] = None
92
+ check_in_end: Optional[str] = None
93
+ check_out: Optional[str] = None
94
+ currency_code: Optional[str] = None
95
+ display_order: Optional[int] = None
96
+ external_display_order: Optional[int] = None
97
+ external_name: Optional[str] = None
98
+ id: Optional[int] = None
99
+ internal_code: Optional[str] = None
100
+ key: Optional[UUID] = None
101
+ latitude: Optional[int] = None
102
+ longitude: Optional[int] = None
103
+ max_adults: Optional[int] = None
104
+ max_children: Optional[int] = None
105
+ max_guests: Optional[int] = None
106
+ max_pets: Optional[int] = None
107
+ name: Optional[str] = None
108
+ owner_id: Optional[int] = None
109
+ property_type: Optional[str] = None
110
+ public_url: Optional[str] = None
111
+ thumbnail_url: Optional[str] = None
112
+ thumbnail_url_large: Optional[str] = None
113
+ thumbnail_url_medium: Optional[str] = None
114
+ living_area: Optional[int] = None
115
+
116
+ def __init__(self, **kwargs):
117
+ for key, value in kwargs.items():
118
+ if hasattr(self, key):
119
+ if key == 'address' and value:
120
+ self.address = Address(**value)
121
+ else:
122
+ setattr(self, key, value)
123
+
124
+
125
+
126
+ @dataclass
127
+ class Charge:
128
+ amount: Optional[int] = None
129
+ commission_amount: Optional[int] = None
130
+ description: Optional[str] = None
131
+ expense_id: Optional[int] = None
132
+ is_channel_managed: Optional[bool] = None
133
+ is_commission_all: Optional[bool] = None
134
+ is_expense_all: Optional[bool] = None
135
+ is_taxable: Optional[bool] = None
136
+ owner_amount: Optional[int] = None
137
+ owner_commission_percent: Optional[int] = None
138
+ position: Optional[int] = None
139
+ rate: Optional[int] = None
140
+ rate_is_percent: Optional[bool] = None
141
+ surcharge_id: Optional[int] = None
142
+ tax_id: Optional[int] = None
143
+ type: Optional[str] = None
144
+
145
+ def __init__(self, **kwargs):
146
+ for key, value in kwargs.items():
147
+ if hasattr(self, key):
148
+ setattr(self, key, value)
149
+
150
+
151
+ @dataclass
152
+ class DoorCode:
153
+ code: Optional[str] = None
154
+ lock_names: Optional[str] = None
155
+
156
+ def __init__(self, **kwargs):
157
+ for key, value in kwargs.items():
158
+ if hasattr(self, key):
159
+ setattr(self, key, value)
160
+
161
+
162
+
163
+ @dataclass
164
+ class Booking:
165
+ adults: Optional[int] = None
166
+ arrival: Optional[datetime] = None
167
+ booked_utc: Optional[datetime] = None
168
+ canceled_utc: Optional[datetime] = None
169
+ charges: Optional[List[Charge]] = None
170
+ check_in: Optional[str] = None
171
+ check_in_end: Optional[str] = None
172
+ check_out: Optional[str] = None
173
+ children: Optional[int] = None
174
+ cleaning_date: Optional[datetime] = None
175
+ currency_code: Optional[str] = None
176
+ departure: Optional[datetime] = None
177
+ door_codes: Optional[List[DoorCode]] = None
178
+ form_key: Optional[str] = None
179
+ guest: Optional[Guest] = None
180
+ guest_id: Optional[int] = None
181
+ id: Optional[int] = None
182
+ infants: Optional[int] = None
183
+ is_block: Optional[bool] = None
184
+ listing_site: Optional[str] = None
185
+ notes: Optional[str] = None
186
+ owner_id: Optional[int] = None
187
+ pending_until_utc: Optional[datetime] = None
188
+ pets: Optional[int] = None
189
+ platform_email_address: Optional[str] = None
190
+ platform_reservation_number: Optional[str] = None
191
+ property_id: Optional[int] = None
192
+ quote_id: Optional[int] = None
193
+ status: Optional[str] = None
194
+ title: Optional[str] = None
195
+ total_amount: Optional[int] = None
196
+ total_host_fees: Optional[int] = None
197
+ total_owed: Optional[int] = None
198
+ total_paid: Optional[int] = None
199
+ total_refunded: Optional[int] = None
200
+ type: Optional[str] = None
201
+
202
+ def __init__(self, **kwargs):
203
+ for key, value in kwargs.items():
204
+ if hasattr(self, key):
205
+ if key in ['arrival', 'departure', 'booked_utc', 'canceled_utc', 'cleaning_date', 'pending_until_utc'] and value:
206
+ # Convert string dates to datetime objects
207
+ try:
208
+ setattr(self, key, datetime.fromisoformat(value.replace('Z', '+00:00')))
209
+ except (ValueError, AttributeError):
210
+ setattr(self, key, value)
211
+ elif key == 'charges' and value:
212
+ self.charges = [Charge(**charge) for charge in value]
213
+ elif key == 'door_codes' and value:
214
+ self.door_codes = [DoorCode(**code) for code in value]
215
+ elif key == 'guest' and value:
216
+ self.guest = Guest(**value)
217
+ else:
218
+ setattr(self, key, value)
@@ -0,0 +1,76 @@
1
+ import requests
2
+ from json import JSONDecodeError
3
+ from ownerrez_wrapper.model import Result
4
+ import base64
5
+ from typing import List, Dict
6
+ import logging
7
+ import datetime
8
+ from ownerrez_wrapper.constants import BASEURL as hosturl
9
+ from ownerrez_wrapper.exceptions import OwnerrezApiException
10
+
11
+ class RestAdapter(object):
12
+ def __init__(self,username, token):
13
+ """
14
+ Initialize the RestAdapter object with the OwnerRez username and token
15
+ :param username: OwnerRez username
16
+ :param token: OwnerRez token
17
+ """
18
+ auth_string = f"{username}:{token}"
19
+ auth_bytes = auth_string.encode('ascii')
20
+ auth_b64 = base64.b64encode(auth_bytes).decode('ascii')
21
+ self.headers = {
22
+ 'User-Agent': 'OwnerRezAPI',
23
+ 'Content-Type': 'application/json',
24
+ 'Authorization': f'Basic {auth_b64}'
25
+ }
26
+ self._logger = logging.getLogger(__name__)
27
+ self._logger.setLevel(logging.DEBUG)
28
+
29
+ def _do_request(self, http_method, endpoint, ep_params, data: Dict = None):
30
+ """
31
+ Make a request to the OwnerRez API
32
+ :param http_method: HTTP method to use
33
+ :param endpoint: API endpoint to call
34
+ :param ep_params: Dictionary of parameters to pass to the endpoint
35
+ :param data: Dictionary of data to pass in the request body
36
+ :return: Result object
37
+ """
38
+
39
+ full_url = f"{hosturl}/{endpoint.lstrip('/')}"
40
+ log_line_pre = f"method={http_method}, url={full_url}, params={ep_params}"
41
+ log_line_post = ', '.join((log_line_pre, "success={}, status_code={}, message={}"))
42
+
43
+ # Make the request
44
+ try:
45
+ self._logger.debug(msg = log_line_pre)
46
+ response = requests.request(method=http_method, url=full_url, headers=self.headers, params=ep_params, json=data)
47
+
48
+ # If the request fails, log the error and raise an exception
49
+ except requests.exceptions.RequestException as e:
50
+ self._logger.error(msg=(str(e)))
51
+ raise OwnerrezApiException("Request failed") from e
52
+
53
+ # Check status code first
54
+ is_success = 299 >= response.status_code >= 200 # 200 to 299 is OK
55
+ if not is_success:
56
+ self._logger.error(msg=log_line_post.format(False, response.status_code, response.reason))
57
+ raise OwnerrezApiException(f"{response.status_code}: {response.reason}")
58
+
59
+ # Try to parse the response as JSON only for successful responses
60
+ try:
61
+ data_out = response.json()
62
+ except (ValueError, TypeError, JSONDecodeError) as e:
63
+ self._logger.error(msg=log_line_post.format(False, response.status_code, e))
64
+ raise OwnerrezApiException("Bad JSON in response") from e
65
+
66
+
67
+ return Result(status=response.status_code, message=response.reason, data=data_out)
68
+
69
+ def get(self, endpoint: str, ep_params: Dict = None) -> Result:
70
+ """
71
+ Make a GET request to the OwnerRez API
72
+ :param endpoint: API endpoint to call
73
+ :param ep_params: Dictionary of parameters to pass to the endpoint
74
+ :return: Result object
75
+ """
76
+ return self._do_request(http_method='GET', endpoint=endpoint, ep_params=ep_params)
@@ -0,0 +1,187 @@
1
+ Metadata-Version: 2.2
2
+ Name: ownerrez-wrapper
3
+ Version: 0.1.0
4
+ Summary: A Python wrapper for the OwnerRez API
5
+ Home-page: https://github.com/gmoorevt/ownerrez-wrapper
6
+ Author: Geody Moore
7
+ Author-email: geody.moore@gmail.com
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: requests>=2.25.0
20
+ Requires-Dist: python-dateutil>=2.8.0
21
+ Requires-Dist: python-dotenv>=1.0.0
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest>=6.0.0; extra == "dev"
24
+ Requires-Dist: pytest-cov>=2.0.0; extra == "dev"
25
+ Requires-Dist: black>=22.0.0; extra == "dev"
26
+ Requires-Dist: isort>=5.0.0; extra == "dev"
27
+ Requires-Dist: flake8>=3.9.0; extra == "dev"
28
+ Requires-Dist: mypy>=0.900; extra == "dev"
29
+ Requires-Dist: freezegun>=1.2.0; extra == "dev"
30
+ Dynamic: author
31
+ Dynamic: author-email
32
+ Dynamic: classifier
33
+ Dynamic: description
34
+ Dynamic: description-content-type
35
+ Dynamic: home-page
36
+ Dynamic: provides-extra
37
+ Dynamic: requires-dist
38
+ Dynamic: requires-python
39
+ Dynamic: summary
40
+
41
+ # OwnerRez API Wrapper
42
+
43
+ A Python wrapper for the OwnerRez API. This package provides a simple and intuitive interface to interact with the OwnerRez API v2.
44
+
45
+ ## Requirements
46
+
47
+ - Python 3.10 or higher
48
+
49
+ ## Installation
50
+
51
+ ```bash
52
+ pip install ownerrez-wrapper
53
+ ```
54
+
55
+ ## Python Usage
56
+
57
+ ```python
58
+ from ownerrez_wrapper import API
59
+ from datetime import datetime
60
+
61
+ # Initialize the API client
62
+ api = API(username="your_username", token="your_token")
63
+
64
+ # Get all properties
65
+ properties = api.getproperties()
66
+ for prop in properties:
67
+ print(f"Property: {prop.name} (ID: {prop.id})")
68
+
69
+ # Get bookings for a property
70
+ bookings = api.getbookings(
71
+ property_id=123,
72
+ since_utc=datetime(2024, 1, 1)
73
+ )
74
+ for booking in bookings:
75
+ print(f"Booking: {booking.arrival} to {booking.departure}")
76
+
77
+ # Get a specific booking
78
+ booking = api.getbooking(booking_id=456)
79
+ print(f"Guest ID: {booking.guest_id}")
80
+
81
+ # Get guest details
82
+ guest = api.getguest(guest_id=789)
83
+ print(f"Guest: {guest.first_name} {guest.last_name}")
84
+
85
+ # Check if a property is currently booked
86
+ is_booked = api.isunitbooked(property_id=123)
87
+ print(f"Property is booked: {is_booked}")
88
+ ```
89
+
90
+ ## API Documentation
91
+
92
+ ### API Class Methods
93
+
94
+ - `getproperties()` -> List[Property]
95
+ - Returns a list of all properties
96
+
97
+ - `getproperty(property_id: int)` -> Property
98
+ - Get details for a specific property
99
+ - Parameters:
100
+ - property_id: The property ID
101
+
102
+ - `getbookings(property_id: int, since_utc: datetime)` -> List[Booking]
103
+ - Get bookings for a specific property since a given date
104
+ - Parameters:
105
+ - property_id: The property ID
106
+ - since_utc: DateTime object for the start date
107
+
108
+ - `getbooking(booking_id: int)` -> Booking
109
+ - Get details for a specific booking
110
+ - Parameters:
111
+ - booking_id: The booking ID
112
+
113
+ - `getguest(guest_id: int)` -> Guest
114
+ - Get details for a specific guest
115
+ - Parameters:
116
+ - guest_id: The guest ID
117
+
118
+ - `isunitbooked(property_id: int)` -> bool
119
+ - Check if a property is currently booked
120
+ - Parameters:
121
+ - property_id: The property ID
122
+
123
+ ### Data Models
124
+
125
+ The API returns strongly-typed objects that provide a rich interface to the OwnerRez data. For detailed documentation of all data models, including all available fields and usage examples, see [Data Models Documentation](docs/models.md).
126
+
127
+ Main models:
128
+ - `Property`: Contains property details (id, name, bedrooms, etc.)
129
+ - `Booking`: Contains booking information (arrival, departure, guest, etc.)
130
+ - `Guest`: Contains guest information (name, contact details, etc.)
131
+
132
+ Supporting models:
133
+ - `Address`: Physical address information
134
+ - `EmailAddress`: Email contact information
135
+ - `Phone`: Phone contact information
136
+ - `Charge`: Booking charge information
137
+ - `DoorCode`: Property access codes
138
+
139
+ ## CLI Tool (for testing)
140
+
141
+ A command-line interface is included for testing and debugging. You can provide credentials via environment variables or a `.env` file:
142
+
143
+ ```bash
144
+ # Using .env file
145
+ echo "OWNERREZ_USERNAME=your_username" > .env
146
+ echo "OWNERREZ_TOKEN=your_token" >> .env
147
+
148
+ # List properties
149
+ ownerrez properties
150
+
151
+ # Get property details
152
+ ownerrez property 123
153
+
154
+ # Check bookings
155
+ ownerrez bookings 123 --since 2024-01-01
156
+
157
+ # Get booking details
158
+ ownerrez booking 456
159
+ ```
160
+
161
+ ## Development
162
+
163
+ To set up the development environment:
164
+
165
+ ```bash
166
+ git clone https://github.com/gmoorevt/ownerrez-wrapper.git
167
+ cd ownerrez-wrapper
168
+ pip install -e ".[dev]"
169
+ ```
170
+
171
+ To run tests:
172
+
173
+ ```bash
174
+ pytest
175
+ ```
176
+
177
+ ## License
178
+
179
+ This project is licensed under the MIT License - see the LICENSE file for details.
180
+
181
+ ## Author
182
+
183
+ - Geody Moore (geody.moore@gmail.com)
184
+
185
+ ## Contributing
186
+
187
+ Contributions are welcome! Please feel free to submit a Pull Request.
@@ -0,0 +1,21 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ requirements-dev.txt
5
+ requirements.txt
6
+ setup.py
7
+ ownerrez_wrapper/__init__.py
8
+ ownerrez_wrapper/api.py
9
+ ownerrez_wrapper/cli.py
10
+ ownerrez_wrapper/constants.py
11
+ ownerrez_wrapper/exceptions.py
12
+ ownerrez_wrapper/model.py
13
+ ownerrez_wrapper/restAdapter.py
14
+ ownerrez_wrapper.egg-info/PKG-INFO
15
+ ownerrez_wrapper.egg-info/SOURCES.txt
16
+ ownerrez_wrapper.egg-info/dependency_links.txt
17
+ ownerrez_wrapper.egg-info/entry_points.txt
18
+ ownerrez_wrapper.egg-info/requires.txt
19
+ ownerrez_wrapper.egg-info/top_level.txt
20
+ tests/__init__.py
21
+ tests/test_api.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ ownerrez = ownerrez_wrapper.cli:main
@@ -0,0 +1,12 @@
1
+ requests>=2.25.0
2
+ python-dateutil>=2.8.0
3
+ python-dotenv>=1.0.0
4
+
5
+ [dev]
6
+ pytest>=6.0.0
7
+ pytest-cov>=2.0.0
8
+ black>=22.0.0
9
+ isort>=5.0.0
10
+ flake8>=3.9.0
11
+ mypy>=0.900
12
+ freezegun>=1.2.0
@@ -0,0 +1,2 @@
1
+ ownerrez_wrapper
2
+ tests
@@ -0,0 +1,9 @@
1
+ -r requirements.txt
2
+ pytest>=6.0.0
3
+ pytest-cov>=2.0.0
4
+ black>=22.0.0
5
+ isort>=5.0.0
6
+ flake8>=3.9.0
7
+ mypy>=0.900
8
+ build>=0.7.0
9
+ twine>=3.4.2
@@ -0,0 +1,2 @@
1
+ requests>=2.25.0
2
+ python-dateutil>=2.8.0
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,48 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ with open("README.md", "r", encoding="utf-8") as fh:
4
+ long_description = fh.read()
5
+
6
+ setup(
7
+ name="ownerrez-wrapper",
8
+ version="0.1.0",
9
+ author="Geody Moore",
10
+ author_email="geody.moore@gmail.com",
11
+ description="A Python wrapper for the OwnerRez API",
12
+ long_description=long_description,
13
+ long_description_content_type="text/markdown",
14
+ url="https://github.com/gmoorevt/ownerrez-wrapper",
15
+ packages=find_packages(),
16
+ classifiers=[
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Operating System :: OS Independent",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ ],
26
+ python_requires=">=3.10",
27
+ install_requires=[
28
+ "requests>=2.25.0",
29
+ "python-dateutil>=2.8.0",
30
+ "python-dotenv>=1.0.0"
31
+ ],
32
+ extras_require={
33
+ "dev": [
34
+ "pytest>=6.0.0",
35
+ "pytest-cov>=2.0.0",
36
+ "black>=22.0.0",
37
+ "isort>=5.0.0",
38
+ "flake8>=3.9.0",
39
+ "mypy>=0.900",
40
+ "freezegun>=1.2.0"
41
+ ]
42
+ },
43
+ entry_points={
44
+ 'console_scripts': [
45
+ 'ownerrez=ownerrez_wrapper.cli:main',
46
+ ],
47
+ },
48
+ )
File without changes
@@ -0,0 +1,124 @@
1
+ """
2
+ Tests for the OwnerRez API wrapper
3
+ """
4
+ import pytest
5
+ from datetime import datetime
6
+ from freezegun import freeze_time
7
+ from ownerrez_wrapper.api import API
8
+ from ownerrez_wrapper.model import Result, Property, Booking, Guest
9
+
10
+ @pytest.fixture
11
+ def api():
12
+ """Create an API instance for testing"""
13
+ return API(username="test_user", token="test_token")
14
+
15
+ @pytest.fixture
16
+ def mock_property_response():
17
+ return {
18
+ "items": [{
19
+ "id": 1,
20
+ "name": "Test Property",
21
+ "bedrooms": 3,
22
+ "bathrooms": 2,
23
+ "active": True
24
+ }]
25
+ }
26
+
27
+ @pytest.fixture
28
+ def mock_booking_response():
29
+ return {
30
+ "items": [{
31
+ "id": 1,
32
+ "property_id": 1,
33
+ "arrival": "2024-03-01",
34
+ "departure": "2024-03-05",
35
+ "status": "confirmed",
36
+ "guest_id": 1
37
+ }]
38
+ }
39
+
40
+ @pytest.fixture
41
+ def mock_guest_response():
42
+ return {
43
+ "id": 1,
44
+ "first_name": "John",
45
+ "last_name": "Doe",
46
+ "email_addresses": [{"address": "john@example.com", "is_default": True}]
47
+ }
48
+
49
+ def test_api_initialization(api):
50
+ """Test API initialization"""
51
+ assert api.username == "test_user"
52
+ assert api.token == "test_token"
53
+
54
+ def test_getproperties(api, mock_property_response, monkeypatch):
55
+ def mock_get(*args, **kwargs):
56
+ return Result(status=200, message="OK", data=mock_property_response)
57
+
58
+ monkeypatch.setattr("ownerrez_wrapper.restAdapter.RestAdapter.get", mock_get)
59
+
60
+ properties = api.getproperties()
61
+ assert len(properties) == 1
62
+ assert isinstance(properties[0], Property)
63
+ assert properties[0].id == 1
64
+ assert properties[0].name == "Test Property"
65
+
66
+ def test_getbookings(api, mock_booking_response, monkeypatch):
67
+ def mock_get(*args, **kwargs):
68
+ return Result(status=200, message="OK", data=mock_booking_response)
69
+
70
+ monkeypatch.setattr("ownerrez_wrapper.restAdapter.RestAdapter.get", mock_get)
71
+
72
+ bookings = api.getbookings(property_id=1, since_utc=datetime(2024, 1, 1))
73
+ assert len(bookings) == 1
74
+ assert isinstance(bookings[0], Booking)
75
+ assert bookings[0].id == 1
76
+ assert bookings[0].property_id == 1
77
+
78
+ def test_getbooking(api, monkeypatch):
79
+ booking_data = {
80
+ "id": 1,
81
+ "property_id": 1,
82
+ "arrival": "2024-03-01",
83
+ "departure": "2024-03-05"
84
+ }
85
+
86
+ def mock_get(*args, **kwargs):
87
+ return Result(status=200, message="OK", data=booking_data)
88
+
89
+ monkeypatch.setattr("ownerrez_wrapper.restAdapter.RestAdapter.get", mock_get)
90
+
91
+ booking = api.getbooking(booking_id=1)
92
+ assert isinstance(booking, Booking)
93
+ assert booking.id == 1
94
+ assert booking.property_id == 1
95
+
96
+ def test_getguest(api, mock_guest_response, monkeypatch):
97
+ def mock_get(*args, **kwargs):
98
+ return Result(status=200, message="OK", data=mock_guest_response)
99
+
100
+ monkeypatch.setattr("ownerrez_wrapper.restAdapter.RestAdapter.get", mock_get)
101
+
102
+ guest = api.getguest(guest_id=1)
103
+ assert isinstance(guest, Guest)
104
+ assert guest.id == 1
105
+ assert guest.first_name == "John"
106
+ assert guest.last_name == "Doe"
107
+
108
+ @freeze_time("2024-03-03") # A date between arrival and departure
109
+ def test_isunitbooked(api, mock_booking_response, monkeypatch):
110
+ def mock_get(*args, **kwargs):
111
+ return Result(status=200, message="OK", data=mock_booking_response)
112
+
113
+ monkeypatch.setattr("ownerrez_wrapper.restAdapter.RestAdapter.get", mock_get)
114
+ is_booked = api.isunitbooked(property_id=1)
115
+ assert is_booked is True
116
+
117
+ @freeze_time("2024-03-06") # A date after departure
118
+ def test_isunitbooked_not_booked(api, mock_booking_response, monkeypatch):
119
+ def mock_get(*args, **kwargs):
120
+ return Result(status=200, message="OK", data=mock_booking_response)
121
+
122
+ monkeypatch.setattr("ownerrez_wrapper.restAdapter.RestAdapter.get", mock_get)
123
+ is_booked = api.isunitbooked(property_id=1)
124
+ assert is_booked is False