Design principles
The Azure SDK should be designed to enhance the productivity of developers connecting to Azure services. Other qualities (such as completeness, extensibility, and performance) are important but secondary. Productivity is achieved by adhering to the principles described below:
Idiomatic
- The SDK should follow the general design guidelines and conventions for the target language. It should feel natural to a developer in the target language.
- We embrace the ecosystem with its strengths and its flaws.
- We work with the ecosystem to improve it for all developers.
Consistent
- Client libraries should be consistent within the language, consistent with the service and consistent between all target languages. In cases of conflict, consistency within the language is the highest priority and consistency between all target languages is the lowest priority.
- Service-agnostic concepts such as logging, HTTP communication, and error handling should be consistent. The developer should not have to relearn service-agnostic concepts as they move between client libraries.
- Consistency of terminology between the client library and the service is a good thing that aids in diagnosability.
- All differences between the service and client library must have a good (articulated) reason for existing, rooted in idiomatic usage rather than whim.
- The Azure SDK for each target language feels like a single product developed by a single team.
- There should be feature parity across target languages. This is more important than feature parity with the service.
Approachable
- We are experts in the supported technologies so our customers, the developers, don’t have to be.
- Developers should find great documentation (hero tutorial, how to articles, samples, and API documentation) that makes it easy to be successful with the Azure service.
- Getting off the ground should be easy through the use of predictable defaults that implement best practices. Think about progressive concept disclosure.
- The SDK should be easily acquired through the most normal mechanisms in the target language and ecosystem.
- Developers can be overwhelmed when learning new service concepts. The core use cases should be discoverable.
Diagnosable
- The developer should be able to understand what is going on.
- It should be discoverable when and under what circumstances a network call is made.
- Defaults are discoverable and their intent is clear.
- Logging, tracing, and exception handling are fundamental and should be thoughtful.
- Error messages should be concise, correlated with the service, actionable, and human readable. Ideally, the error message should lead the consumer to a useful action that they can take.
- Integrating with the preferred debugger for the target language should be easy.
Dependable
- Breaking changes are more harmful to a user’s experience than most new features and improvements are beneficial.
- Incompatibilities should never be introduced deliberately without thorough review and very strong justification.
- Do not rely on dependencies that can force our hand on compatibility.
Python Design Principles
Use the guiding principles in the Zen of Python Zen of Python when making design trade-offs:
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
General Guidelines
✅ DO follow the General Azure SDK Guidelines.
✅ DO locate all source code in the azure/azure-sdk-for-python GitHub repository.
✅ DO follow Azure SDK engineering systems guidelines for working in the azure/azure-sdk-for-python GitHub repository.
Supported python versions
✅ DO support Python 2.7 and 3.5.3+.
☑️ YOU SHOULD provide a universal package that works on all supported versions of Python, unless there’s a compelling reason to have separate Python2 and Python3 packages.
For example, if you depend on different external packages for Python2 and Python3, and neither external dependency is available for both Python versions.
✅ DO provide both source distributions (sdist
) and wheels.
✅ DO publish both source distributions (sdist
) and wheels to PyPI.
✅ DO test correct behavior for both CPython and PyPy for pure and universal Python wheels.
For more information on packaging naming, see the Packaging section.
Code style
✅ DO follow the general guidelines in PEP8 unless explicitly overridden in this document.
⛔️ DO NOT “borrow” coding paradigms from other languages.
For example, no matter how common Reactive programming is in the Java community, it’s still unfamiliar for most Python developers.
✅ DO favor consistency with other Python components over other libraries for the same service.
It’s more likely that a developer will use many different libraries using the same language than a developer will use the same service from many different languages.
Naming conventions
✅ DO use snake_case for variable, function, and method names:
# Yes:
service_client = ServiceClient()
service_client.list_things()
def do_something():
...
# No:
serviceClient = ServiceClient()
service_client.listThings()
def DoSomething():
...
✅ DO use Pascal case for types:
# Yes:
class ThisIsCorrect(object):
pass
# No:
class this_is_not_correct(object):
pass
# No:
class camelCasedTypeName(object):
pass
✅ DO use ALL CAPS for constants:
# Yes:
MAX_SIZE = 4711
# No:
max_size = 4711
# No:
MaxSize = 4711
✅ DO use snake_case for module names.
Method signatures
⛔️ DO NOT use static methods (staticmethod
). Prefer module level functions instead.
Static methods are rare and usually forced by other libraries.
⛔️ DO NOT use simple getter and setter functions. Use properties instead.
# Yes
class GoodThing(object):
@property
def something(self):
""" Example of a good read-only property."""
return self._something
# No
class BadThing(object):
def get_something(self):
""" Example of a bad 'getter' style method."""
return self._something
⚠️ YOU SHOULD NOT have methods that require more than five positional parameters. Optional/flag parameters can be accepted using keyword-only arguments, or **kwargs
.
✅ DO use keyword-only arguments for optional or less-often-used arguments for modules that only need to support Python 3.
# Yes
def foo(a, b, *, c, d=None):
# Note that I can even have required keyword-only arguments...
...
✅ DO use keyword-only arguments for arguments that have no obvious ordering.
# Yes - `source` and `dest` have logical order, `recurse` and `overwrite` do not.
def copy(source, dest, *, recurse=False, overwrite=False) ...
# No
def copy(source, dest, recurse=False, overwrite=False) ...
✅ DO specify the parameter name when calling methods with more than two required positional parameters.
def foo(a, b, c):
pass
def bar(d, e):
pass
# Yes:
foo(a=1, b=2, c=3)
bar(1, 2)
bar(e=3, d=4)
# No:
foo(1, 2, 3)
✅ DO specify the parameter name for optional parameters when calling functions.
def foo(a, b=1, c=None):
pass
# Yes:
foo(1, b=2, c=3)
# No:
foo(1, 2, 3)
Public vs “private”
✅ DO use a single leading underscore to indicate that a name isn’t part of the public API. Non-public APIs aren’t guaranteed to be stable.
⛔️ DO NOT use leading double underscore prefixed method names unless name clashes in the inheritance hierarchy are likely. Name clashes are rare.
✅ DO add public methods and types to the module’s __all__
attribute.
✅ DO use a leading underscore for internal modules. You may omit a leading underscore if the module is a submodule of an internal module.
# Yes:
azure.exampleservice._some_internal_module
# Yes - some_internal_module is still considered internal since it is a submodule of an internal module:
azure.exampleservice._internal.some_internal_module
# No - some_internal_module is considered public:
azure.exampleservice.some_internal_module
Types (or not)
✅ DO prefer structural subtyping and protocols over explicit type checks.
✅ DO derive from the abstract collections base classes collections.abc
(or collections
for Python 2.7) to provide custom mapping types.
✅ DO provide type hints PEP484 for publicly documented classes and functions.
- See the suggested syntax for Python 2.7 and 2.7-3.x straddling code for guidance for Python 2.7 compatible code.
Threading
✅ DO maintain thread affinity for user-provided callbacks unless explicitly documented to not do so.
✅ DO explicitly include the fact that a method (function/class) is thread safe in its documentation.
Examples: asyncio.loop.call_soon_threadsafe
, queue
☑️ YOU SHOULD allow callers to pass in an Executor
instance rather than defining your own thread or process management for parallelism.
You may do your own thread management if the thread isn’t exposed to the caller in any way. For example, the LROPoller
implementation uses a background poller thread.