ownerrez-wrapper 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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