Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to organize Python API module to make it neat?

I'm writing a Python library that represents some web API. Right now, my library directory looks close to this:

  • __init__.py
  • Account.py
  • Order.py
  • Category.py
  • requests.py

In __init__.py, I have something like this:

from .Account import Account
from .Order import Order
from .Category import Category
from . import requests

This allows to use import cool_site and then cool_site.Account(…) and so on, but it has a following problem: when I play around with my code in IDLE, the object is then called cool_site.Account.Account, which I feel is bad.

1. Is there any way to avoid class name duplication and still have separate file for every class?

The next thing I don't feel great about is my code organization. Right now, my Account class takes credentials on initialization, creates a requests.Session object and then handles all communication with server, i.e. searching for orders and so on. This Account class instance will then pass itself to all other instances, for example to Order - so the order's instance will have .account property holding the Account instance which created it. When another class instance itself has to do something, for example change an order's comment (by calling o.comment = 'new comment', so by @comment.setter decorator in the Order class), it forwards that to an Account object which is passed to it on initialization, and then uses for example self.account.set_order_comment(new_comment). This method will then use all the web requests to achieve that goal.

2. Is it better to hold server communication logic in one class or to spread different aspects of it to classes that are affected by them?

The last thing I'd like to ask about is how and where to keep low-level request templates. Right now I have it in cool_site.requests submodule, and there are different functions for different requests, for example SetOrderComment for the case mentioned above (it's a function, so it should be lowercase, but in this case I think it resembles a class in a way - is that OK?). The Account.set_order_comment will use it like this:

r = cool_site.requests.SetOrderComment(order.id, new_comment)
response = self._session.request(**r)

because this function returns a dict with arguments to Session.request function from requests library. The authentication headers are already set in the _session property of Account class instance. I feel it's a little bit ugly, but I don't have any better idea.

3. How to organize web requests to keep it all clean?

Post scriptum

I'm sorry this question is so long and covers many aspects of API library design, but all the tips will be appreciated. In a way, all of the three questions above could be expressed as "How to do it better and cleaner?" or "How most of the Python developers do it?", or maybe even "What would feel most Pythonic?".

Throw at me any little tips you can think of.

like image 599
Anonymouse Avatar asked Sep 27 '19 19:09

Anonymouse


People also ask

How do you organize Python modules?

Organize your modules into packages. Each package must contain a special __init__.py file. Your project should generally consist of one top-level package, usually containing sub-packages. That top-level package usually shares the name of your project, and exists as a directory in the root of your project's repository.

Why should you organize your programs in functions modules and packages?

Modules and packages aren't just there to spread your Python code across multiple source files and directories—they allow you to organize your code to reflect the logical structure of what your program is trying to do.


2 Answers

I've been thinking about very similar things lately working on wistiapy. Examples of my current thinking about client code organisation are in there. YMMV.

  1. "One class per file" is more of a Java style guideline than Python. Python modules are a legitimate and important level in the code hierarchy, and you shouldn't worry about having more than one function or class in the same module. You could put all the model classes in a .models module, and then from .models import (Account, Order, Category) in __init__.py.

  2. More-or-less common practice for client libraries seems to be to have a client module, containing something like a MyServiceClient class. (eg the Segment client). This is where the networking logic goes. If you want to have the public interface be module-level functions, you can do some clever stuff with creating a default client and having the functions call methods on that.

Functions should be snake_case, classes should be PascalCase. Doing anything else tends to cause more confusion than benefit.

It seems like the big question you're dealing with is trying to choose between the "Active Record" pattern (some_account.set_order_comment(comment)), and the "Data Mapper" pattern (set_order_comment(account, comment)). Either will work and they each have their benefits and drawbacks. I've found the data mapper pattern -- using smart functions to manipulate fairly simple data classes -- simpler to begin with.

I find it helpful to design the public interface concurrently with something that uses that interface. In the calling code, you can write what you'd like to have to call, and then implement the client code "outside-in".

like image 76
Fush Avatar answered Oct 25 '22 16:10

Fush


1) no upper case in names of .py file (also try to avoid _) so your files should be

__init__.py
account.py
order.py
category.py
requests.py

2) if you want to use like cool_site.Account you need to add to __init__.py

from .account import Account
from .order import Order
from .category import Category

__all__ = [
    'Account',
    'Order',
    'Category',
]

3) SetOrderComment is bad name, use set_order_comment

4) If you write a python wrapper for communication with API, make method that do authorisation and other other stuff that is same in every API request. This method should take as params part of requests kwargs that are different for different API calls

for example

class API:
    def __init__(self, endpoint:s str, api_key: str):
        self.endpoint = endpoint
        self.api_key = api_key

    def _get_auth_headers(self) -> Dict[str, str]:
        return {
           'Authorization': 'Bearer ' + self.api_key,
        }

    def get(self, path, params)
         resp = requester.get(
            self.endpoint + path,
            headers=self._get_auth_headers(),
            params=params,
            timeout=30,
        )
        self._check_api_response(resp)
        payload = resp.json()
        return payload

5) If you write a python API look at flask and django frameworks and projects with API written on them. You should find some good ides overthere.

like image 4
Ryabchenko Alexander Avatar answered Oct 25 '22 17:10

Ryabchenko Alexander