1

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?

2
  • Get something that works first, then worry about whether it is Pythonic. Commented Apr 23, 2018 at 13:32
  • 1
    Not your question but kwargs['first_name'] if 'first_name' in kwargs else None could be kwargs.get('first_name') Commented Apr 23, 2018 at 13:41

1 Answer 1

2

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
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.