Reputation: 22847
Say I have two REST API endpoints (http://example.com/task
, http://example.com/employee
) and the following two classes:
class Employee(object):
def __init__(self, **kwargs):
self.ID = kwargs['ID']
self.first_name = kwargs['first_name'] if 'first_name' in kwargs else None
self.last_name = kwargs['last_name'] if 'last_name' in kwargs else None
class Task(object):
def __init__(self, **kwargs):
self.ID = kwargs['ID']
self.start_date = kwargs['start_date']
self.employee = Employee(ID=kwargs['employee_id'])
I now want to create Python objects from the json returned by the REST API.
Say that a GET to http://example.com/task/19
returns the following json:
json_data = {'ID': '19', 'start_date': '2018-04-23', 'employee_id': 'xyz1223'}
task = Task(**json_data)
Now, the following all work
print(task.ID) # 19
print(task.start_date) # 2018-04-23
print(task.employee.ID) # xyz1223
but print(task.employee.first_name)
returns None
. What I would like to happen is that under the hood a GET request to http://example.com/employee/xyz1223
is sent, that the resulting json is parsed, and that the attributes first_name
and last_name
are filled in.
What is the most pythonic way of doing this?
Upvotes: 1
Views: 1738
Reputation: 531265
I would keep the __init__
methods as simple and as stupid as possible,
which means no I/O. That work is deferred to a class method named from_api
, which just takes the requested ID as an argument, then builds the appropriate URI, parses the output, and passes the necessary data to
the call to __init__
.
Employee.from_api
is still pretty simple, but Task.from_api
is where you implement the "join" by calling Employee.from_api
with an argument taken from the parsed task JSON data.
class Employee(object):
BASE_URL = "http://example.com/employee/"
def __init__(self, id_, first, last):
self.id_ = id_
self.first_name = first
self.last_name = last
@classmethod
def from_api(cls, id_):
url = cls.BASE_URL + id_
json_data = requests.get(url).json()
return Employee(id_=id_
first=json_data.get('first_name'),
last=json_data.get('last_name'))
class Task(object):
BASE_URL = "http://example.com/task/"
def __init__(self, id_, start_date, emp):
self.ID = id_
self.start_date = start_date
self.employee = emp
@classmethod
def from_api(cls, id_):
url = cls.BASE_URL + id_
json_data = requests.get(url).json()
emp = Employee.from_api(json_data['employee_id'])
return Task(id_, json_data['start_date'], emp)
task = Task.from_api("19")
Such a design is well-suited to Python 3.7's forthcoming data classes
@dataclass
class Employee:
id_: str
first_name: str
last_name: str
BASE_URL: ClassVar[str] = "http://example.com/employee/"
@classmethod
def from_api(self, id_):
# same as above
@dataclass
class Task:
id_: str
start_data: str # Or maybe datetime.date, depending on your design
employee: Employee
BASE_URL: ClassVar[str] = "http://example.com/task/"
@classmethod
def from_api(self, id_):
# same as above
Upvotes: 2