Code Monkey home page Code Monkey logo

python-sdk's Introduction

OpenFeature Logo

OpenFeature Python SDK

Specification Latest version
Build status Codecov Min python version Repo status

OpenFeature is an open specification that provides a vendor-agnostic, community-driven API for feature flagging that works with your favorite feature flag management tool.

๐Ÿš€ Quick start

Requirements

  • Python 3.8+

Install

Pip install

pip install openfeature-sdk==0.7.1

requirements.txt

openfeature-sdk==0.7.1
pip install -r requirements.txt

Usage

from openfeature import api
from openfeature.provider.in_memory_provider import InMemoryFlag, InMemoryProvider

# flags defined in memory
my_flags = {
  "v2_enabled": InMemoryFlag("on", {"on": True, "off": False})
}

# configure a provider
api.set_provider(InMemoryProvider(my_flags))

# create a client
client = api.get_client()

# get a bool flag value
flag_value = client.get_boolean_value("v2_enabled", False)
print("Value: " + str(flag_value))

๐ŸŒŸ Features

Status Features Description
โœ… Providers Integrate with a commercial, open source, or in-house feature management tool.
โœ… Targeting Contextually-aware flag evaluation using evaluation context.
โœ… Hooks Add functionality to various stages of the flag evaluation life-cycle.
โœ… Logging Integrate with popular logging packages.
โœ… Domains Logically bind clients with providers.
โœ… Eventing React to state changes in the provider or flag management system.
โœ… Shutdown Gracefully clean up a provider during application shutdown.
โœ… Extending Extend OpenFeature with custom providers and hooks.

Implemented: โœ… | In-progress: โš ๏ธ | Not implemented yet: โŒ

Providers

Providers are an abstraction between a flag management system and the OpenFeature SDK. Look here for a complete list of available providers. If the provider you're looking for hasn't been created yet, see the develop a provider section to learn how to build it yourself.

Once you've added a provider as a dependency, it can be registered with OpenFeature like this:

from openfeature import api
from openfeature.provider.no_op_provider import NoOpProvider

api.set_provider(NoOpProvider())
open_feature_client = api.get_client()

In some situations, it may be beneficial to register multiple providers in the same application. This is possible using domains, which is covered in more detail below.

Targeting

Sometimes, the value of a flag must consider some dynamic criteria about the application or user, such as the user's location, IP, email address, or the server's location. In OpenFeature, we refer to this as targeting. If the flag management system you're using supports targeting, you can provide the input data using the evaluation context.

from openfeature.api import (
    get_client,
    get_provider,
    set_provider,
    get_evaluation_context,
    set_evaluation_context,
)

global_context = EvaluationContext(
    targeting_key="targeting_key1", attributes={"application": "value1"}
)
request_context = EvaluationContext(
    targeting_key="targeting_key2", attributes={"email": request.form['email']}
)

## set global context
set_evaluation_context(global_context)

# merge second context
client = get_client(name="No-op Provider")
client.get_string_value("email", "fallback", request_context)

Hooks

Hooks allow for custom logic to be added at well-defined points of the flag evaluation life-cycle. Look here for a complete list of available hooks. If the hook you're looking for hasn't been created yet, see the develop a hook section to learn how to build it yourself.

Once you've added a hook as a dependency, it can be registered at the global, client, or flag invocation level.

from openfeature.api import add_hooks
from openfeature.flag_evaluation import FlagEvaluationOptions

# set global hooks at the API-level
add_hooks([MyHook()])

# or configure them in the client
client = OpenFeatureClient()
client.add_hooks([MyHook()])

# or at the invocation-level
options = FlagEvaluationOptions(hooks=[MyHook()])
client.get_boolean_flag("my-flag", False, flag_evaluation_options=options)

Logging

The OpenFeature SDK logs to the openfeature logger using the logging package from the Python Standard Library.

Domains

Clients can be assigned to a domain. A domain is a logical identifier which can be used to associate clients with a particular provider. If a domain has no associated provider, the global provider is used.

from openfeature import api

# Registering the default provider
api.set_provider(MyProvider());
# Registering a provider to a domain
api.set_provider(MyProvider(), "my-domain");

# A client bound to the default provider
default_client = api.get_client();
# A client bound to the MyProvider provider
domain_scoped_client = api.get_client("my-domain");

Domains can be defined on a provider during registration. For more details, please refer to the providers section.

Eventing

Events allow you to react to state changes in the provider or underlying flag management system, such as flag definition changes, provider readiness, or error conditions. Initialization events (PROVIDER_READY on success, PROVIDER_ERROR on failure) are dispatched for every provider. Some providers support additional events, such as PROVIDER_CONFIGURATION_CHANGED.

Please refer to the documentation of the provider you're using to see what events are supported.

from openfeature import api
from openfeature.provider import ProviderEvent

def on_provider_ready(event_details: EventDetails):
    print(f"Provider {event_details.provider_name} is ready")

api.add_handler(ProviderEvent.PROVIDER_READY, on_provider_ready)

client = api.get_client()

def on_provider_ready(event_details: EventDetails):
    print(f"Provider {event_details.provider_name} is ready")

client.add_handler(ProviderEvent.PROVIDER_READY, on_provider_ready)

Shutdown

The OpenFeature API provides a shutdown function to perform a cleanup of all registered providers. This should only be called when your application is in the process of shutting down.

from openfeature import api

api.shutdown()

Extending

Develop a provider

To develop a provider, you need to create a new project and include the OpenFeature SDK as a dependency. This can be a new repository or included in the existing contrib repository available under the OpenFeature organization. Youโ€™ll then need to write the provider by implementing the AbstractProvider class exported by the OpenFeature SDK.

from typing import List, Optional, Union

from openfeature.evaluation_context import EvaluationContext
from openfeature.flag_evaluation import FlagResolutionDetails
from openfeature.hook import Hook
from openfeature.provider import AbstractProvider, Metadata

class MyProvider(AbstractProvider):
    def get_metadata(self) -> Metadata:
        ...

    def get_provider_hooks(self) -> List[Hook]:
        return []

    def resolve_boolean_details(
        self,
        flag_key: str,
        default_value: bool,
        evaluation_context: Optional[EvaluationContext] = None,
    ) -> FlagResolutionDetails[bool]:
        ...

    def resolve_string_details(
        self,
        flag_key: str,
        default_value: str,
        evaluation_context: Optional[EvaluationContext] = None,
    ) -> FlagResolutionDetails[str]:
        ...

    def resolve_integer_details(
        self,
        flag_key: str,
        default_value: int,
        evaluation_context: Optional[EvaluationContext] = None,
    ) -> FlagResolutionDetails[int]:
        ...

    def resolve_float_details(
        self,
        flag_key: str,
        default_value: float,
        evaluation_context: Optional[EvaluationContext] = None,
    ) -> FlagResolutionDetails[float]:
        ...

    def resolve_object_details(
        self,
        flag_key: str,
        default_value: Union[dict, list],
        evaluation_context: Optional[EvaluationContext] = None,
    ) -> FlagResolutionDetails[Union[dict, list]]:
        ...

Built a new provider? Let us know so we can add it to the docs!

Develop a hook

To develop a hook, you need to create a new project and include the OpenFeature SDK as a dependency. This can be a new repository or included in the existing contrib repository available under the OpenFeature organization. Implement your own hook by creating a hook that inherits from the Hook class. Any of the evaluation life-cycle stages (before/after/error/finally_after) can be override to add the desired business logic.

from openfeature.hook import Hook

class MyHook(Hook):
    def after(self, hook_context: HookContext, details: FlagEvaluationDetails, hints: dict):
        print("This runs after the flag has been evaluated")

Built a new hook? Let us know so we can add it to the docs!

โญ๏ธ Support the project

๐Ÿค Contributing

Interested in contributing? Great, we'd love your help! To get started, take a look at the CONTRIBUTING guide.

Thanks to everyone who has already contributed

Pictures of the folks who have contributed to the project

Made with contrib.rocks.

python-sdk's People

Contributors

agardnerit avatar ajhelsby avatar alexsjones avatar beeme1mr avatar chihweilhbird avatar federicobond avatar github-actions[bot] avatar gruebel avatar jamescarr avatar keelerm84 avatar mowies avatar mschoenlaub avatar oleg-nenashev avatar renovate[bot] avatar rgrassian-split avatar snyk-bot avatar tcarrio avatar thomaspoignant avatar toddbaert avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

python-sdk's Issues

Readme needs import of NoOpProvider

The readme currently suggests copy and paste code of:

from open_feature import open_feature_api

open_feature_api.set_provider(NoOpProvider())
open_feature_client = open_feature_api.get_client()

But this doesn't import hte NoOpProvider so to be runnable - the readme snippet actually needs to be:

from open_feature import open_feature_api
from open_feature.provider.no_op_provider import NoOpProvider

open_feature_api.set_provider(NoOpProvider())
open_feature_client = open_feature_api.get_client()

Update SDK to be compliant with spec v0.5.0

Update the SDK to be compliant with the v0.5.0 spec.

What's Changed

โš  BREAKING CHANGES

Additional changes

General

  • Add/update a spec version badge on the readme.

Specification

Full Changelog: open-feature/spec@v0.4.0...v0.5.0

Wording - Should we call objects dictionaries?

It is not common to call dictionaries in python "objects", are we trying to use the same wording for all languages? I see that spec defines Structure so in my opinion Dictionaries would be more appealing to python developers

FlagType enum definition causes problems with mypy

Running mypy on the sdk codebase I found several errors. One of them stood out:

open_feature/open_feature_client.py:202: error: Argument 1 to "evaluate_flag_details" of "OpenFeatureClient" has incompatible type "<typing special form>"; expected "FlagType"  [arg-type]

This corresponds to the following call:

return self.evaluate_flag_details(
FlagType.OBJECT,
flag_key,
default_value,
evaluation_context,
flag_evaluation_options,
)

It appears that mypy has problems recognizing the value of FlagType.OBJECT as a member of FlagType.

class FlagType(Enum):
    BOOLEAN = bool
    STRING = str
    OBJECT = typing.Union[dict, list]
    FLOAT = float
    INTEGER = int

As we can see, OBJECT is the only value that is not a standard type class.

Using types as enum values like this is not very common and has the potential to cause issues with other tools too. Moving to standard str or int enum values may avoid such problems. If there is consensus in this I can open a pull request with the necessary changes.

Create Provider Specific Evaluation Details Object

Currently the Provider is expected to return a FlagEvaluationDetails object, the same that the client does. However it would probably be better to return a specific ProviderEvaluationDetails object instead so that the flag name does not need to be added and if one changes the other does not have to as well. We do this for the other sdks as well so this would make this sdk similar to them

Changes to support spec `v0.6.0` compliance

โš ๏ธ 0.6.0 should probably be undertaken only after the existing 1.0-functional requirements are complete.

Implement specification v0.6.0 changes

See changes since last release.

Please note there's no need to complete all these tasks in a single PR (in fact, that's not recommended). Consider creating a new sub issue for each of the below requirements.

Functional requirements:

Non-functional requirements:

  • add relevant tests
  • add examples and documentation, see template README
  • update specification badge to v0.6.0: Specification

Keep in mind:

  • backwards compatibility is highly encouraged
    • take every precaution to ensure existing code written by application authors and existing providers implementations, do not break
  • it's recommended these features are released together

[BUG] ExceptionObject has no attribute error_message

error_code=e.error_message,

e.error_message is hardcoded but error_message isn't always available.

  File "/usr/local/lib/python3.9/site-packages/open_feature/open_feature_client.py", line 78, in get_string_value
    return self.evaluate_flag_details(
  File "/usr/local/lib/python3.9/site-packages/open_feature/open_feature_client.py", line 228, in evaluate_flag_details
    error_code=e.error_message,
AttributeError: 'ConnectTimeout' object has no attribute 'error_message'

To Recreate

Add a provider with this signature

def get_string_details(
        self,
        key: str,
        default_value: str,
        evaluation_context: EvaluationContext = None,
        flag_evaluation_options: typing.Any = None,
    ):
      raise("foobar exception")

AttributeError: KeyError has no attribute 'error_message'

value = client.get_boolean_details("requestsModuleEnabled",False)

and

{
    "flags": {
      "requestsModuleEnabled": {
        "state": "ENABLED",
        "variants": {
          "on": true,
          "off": false
        },
        "defaultVariant": "off"
      }
    }
}

Results in:

value = client.get_boolean_details("requestsModuleEnabled",False)
  File "/usr/local/lib/python3.10/site-packages/open_feature/open_feature_client.py", line 63, in get_boolean_details
    return self.evaluate_flag_details(
  File "/usr/local/lib/python3.10/site-packages/open_feature/open_feature_client.py", line 228, in evaluate_flag_details
    error_code=e.error_message,
AttributeError: 'KeyError' object has no attribute 'error_message'

automated publishing to pip

The readme says that the SDK can be installed via pip but that doesn't appear to be the case. A GitHub action should be created to publish to automate the process of pushing to pip.

Create end-to-end test suite

Leverage the test-harness and evaluation.feature gherkin suite to create a set of integration tests for the Python SDK. See equivalent examples in Java and JS. Though we should call these "end-to-end" not integration tests, since that's what they are.

The suite should start the test harness container and run it in CI (again, see references in Java and JS).

NOTE: we'll need the python flagd provider first, but work can begin now with this partial version.

Definition of done:

  • gherkin test suite complete and runs on CI
  • updates to documentation (CONTRIBUTING.md)

Implement API-level hooks

There are no global hooks nor way to add hooks to the global API client.

This is required as per the spec.

Definition of done:

  • hooks can be added to the global API, and are run with flag evaluation
  • associated tests

Python SDK

Requirements

  • spec compliant
  • #96
  • test suite with coverage of all relevant specification points (see JS-SDK for example) (tests for each section of spec)
    • API tests
    • client tests
    • provider tests
    • hook tests
    • additional, SDK-specific tests (thread safety, etc) if applicable
  • #99
  • #9
  • #100

Resources

Optimize our GitHub CI workflows

Our current workflows run several steps like black, flake8, and CodeQL repeatedly for each Python version specified in the version matrix. The output of these tools should rarely vary for different Python versions so it makes sense to run them in a single separate flow and only keep the tests running in different Python versions.

This should significantly reduce the build time.

FlagResolutionDetails reason field should accept any string

The specification for resolution details states that the reason field should be an optional string. There are some pre-defined reason values (reflected in the Reason enum), but a provider should be free to provide an arbitrary reason.

I believe the FlagResolutionDetails.reason field should be updated to store an Optional[str] instead of the current Optional[Reason].

Happy to work on the PR if this seems acceptable.

See the related issue on the SDK spec.

0.7.0 breaking changes - compatibility tracking issue

With 0.7.0, we are taking a more strict approach to exporting names in our public API. In #306 we began using __all__ inside each package to explicitly declare the names that are exported from it, thus enforcing a canonical import path for each class/function.

Package maintainers: if you were previously importing names from non-canonical locations, you will need to update your imports to point to the canonical ones. Your code should then be able to work with both current and 0.7.0 SDK versions.

We will be releasing 0.7.0 next week probably, and we will be following it up with a 1.0 shortly after ๐ŸŽ‰

If you have any other questions, feel free to leave a comment here.

Packages that may need updating

Ensure error hooks are run when provider returns FlagResolutionDetails with error code

Currently, in error situations, it's possible for providers to return FlagResolutionDetails objects with errors set, OR throw. However, the SDK only runs the error hooks when an error is thrown.

It should either be impossible to return an object indicating an error (we'd need a new type) or the SDK should check the returned object and run the error hooks if an error is indicated.

See

error_hooks(flag_type, hook_context, err, reversed_merged_hooks, hook_hints)

Update SDK to be compliant with spec v0.2.0

Update the SDK to be compliant with the v0.2.0 spec.

Flag Evaluation API

  • #116 - Have different methods for float and int

Providers

  • #119 - Remove context transformers. Add provider hooks

Evaluation Context

  • #117 - Ensure context merging behavior is correct
  • #120 - Add the ability to fetch all custom fields
  • #121 - Ensure evaluation context keys are unique

Hooks

  • #119 - Ensure provider hook are correctly ordered

Full Changelog: open-feature/spec@v0.1.0...v0.2.0

[FEATURE] Make provider interface "stateless", SDK maintains provider state

Stateless providers

Providers no longer maintain their own state: the state for each provider is maintained in the SDK automatically, and updated according to the success/failures of lifecycle methods (init/shutdown) or events emitted from providers spontaneously.

Tasks

Safe handler dictionary iteration

I got this stack trace while running tests. I imagine it is a very rare race condition:

Can we make the handler data object safe for multi-threading?

tests/e2e/inprocess/grpc/test_inprocess_grpc_reconnect.py::test_provider_unavailable
  /Users/c.bailey/Library/Application Support/hatch/env/virtual/openfeature-provider-flagd/2sJ2q5Uj/openfeature-provider-flagd/lib/python3.9/site-packages/_pytest/threadexception.py:77: PytestUnhandledThreadExceptionWarning: Exception in thread Thread-3
  
  Traceback (most recent call last):
    File "/Users/c.bailey/.pyenv/versions/3.9.13/lib/python3.9/threading.py", line 980, in _bootstrap_inner
      self.run()
    File "/Users/c.bailey/.pyenv/versions/3.9.13/lib/python3.9/threading.py", line 917, in run
      self._target(*self._args, **self._kwargs)
    File "/Users/c.bailey/Source/python-sdk-contrib/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/connector/grpc_watcher.py", line 95, in sync_flags
      self.provider.emit_provider_error(
    File "/Users/c.bailey/Library/Application Support/hatch/env/virtual/openfeature-provider-flagd/2sJ2q5Uj/openfeature-provider-flagd/lib/python3.9/site-packages/openfeature/provider/provider.py", line 81, in emit_provider_error
      self.emit(ProviderEvent.PROVIDER_ERROR, details)
    File "/Users/c.bailey/Library/Application Support/hatch/env/virtual/openfeature-provider-flagd/2sJ2q5Uj/openfeature-provider-flagd/lib/python3.9/site-packages/openfeature/provider/provider.py", line 87, in emit
      run_handlers_for_provider(self, event, details)
    File "/Users/c.bailey/Library/Application Support/hatch/env/virtual/openfeature-provider-flagd/2sJ2q5Uj/openfeature-provider-flagd/lib/python3.9/site-packages/openfeature/_event_support.py", line 75, in run_handlers_for_provider
      for client in _client_handlers:
  RuntimeError: dictionary changed size during iteration
  
    warnings.warn(pytest.PytestUnhandledThreadExceptionWarning(msg))

Use requirements.txt instead of requirements-dev

To align with the contents of the readme and python standard practices the requirements.txt file should not be empty. It should match the dev in the main branch. If dev is different it should be in a feature branch to test dependency upgrades.

get_object should be able to return both dict and list

The SDK is not able to return a list while calling get_object_*.
Other SDKs can return both list and dict with the get_object functions.

The AbstractProvider request to implement resolve_object_details and return a FlagEvaluationDetails[dict] object.
If we want to support both types we should return a FlagEvaluationDetails[Union[dict,list]] object.

[FEATURE] Implement domain scoping

Overview

A domain is an identifier that logically binds clients with providers, allowing multiple providers to be used simultaneously within a single application.

Domains were added to the spec in the following PR: open-feature/spec#229

Tasks

Tasks

Reference implementations

Support was added in the JS SDK here and the Python SDK here.

Note

Some SDKs support named clients which predate domains. The domain term is intended to clarify intended behavior.

Review public APIs (make sure they are "Pythonic")

We know some of the internals of the SDK are a bit "Java-y"; these can be refactored later. However, before a 1.0 we need to make sure the public APIs are idiomatic to ensure we don't regret or design choices post-1.0.

Definition of done:

  • Review SDK, issues created for public API changes if necessary.

Review thread safety of client/API/Evaluation context

Review the thread safety of the SDK to make sure there's no potential concurrency issues, particularly around state maintained in the global API object, clients, and evaluation context objects.

See here for a similar discussion in the Java SDK and others.

Definition of done:

  • issues created for thread safety of global API, client, and evaluation context.

EvaluationDetails should be a string, SDK should export list of standard Reasons.

The EvaluationDetails.reason should be a string, and the SDK should export a default set of reasons, which include the current enum value as well as STATIC and CACHED.

See: https://openfeature.dev/specification/types/#resolution-details
See also https://github.com/open-feature/spec/releases/tag/v0.5.0 and https://github.com/open-feature/spec/releases/tag/v0.5.2)

Definition of done:

  • EvaluationDetails.reason is made an optional string
  • SDK exports set of standard reasons, including STATIC and CACHED, with inline documentation matching the table here.

Implement API-Level Events

There are no signals for various events that happen

This is optional as per the spec but useful for integrators.

Definition of done:

  • Event Handlers are registered via the global API
  • Events are emitted internally when they occur
  • associated tests

[FEATURE] Implement transaction context

Requirements

We should implement transaction context propagation

What is transaction context?

From the spec:
Transaction context is a container for transaction-specific evaluation context (e.g. user id, user agent, IP).
Transaction context can be set where specific data is available (e.g. an auth service or request handler) and by using the transaction context propagator it will automatically be applied to all flag evaluations within a transaction (e.g. a request or thread).

Transaction context has been added in the following PR: open-feature/spec#227

Examples:

Implementation

The JS SDK implementation is implemented as a POC that is currently being hardened.
https://github.com/open-feature/js-sdk/blob/main/packages/server/src/transaction-context/transaction-context.ts
We'd need to define some interfaces to support the construction various context propagation mechanisms, for instance, one based on the flask context.

Usage

An example usage for the JS SDK implementation of the feature is the usage for propagating HTTP request information in the NestJS SDK.
https://github.com/open-feature/js-sdk/blob/main/packages/nest/src/evaluation-context-interceptor.ts#L31

Automate Release Process

Requirements

  • Create a GitHub release automatically based on a git tag
  • The git tag should match v*.*.* and follow semver guidelines.
  • Breaking changes should be represented in PRs and Release notes
  • Breaking changes should NOT bump major version numbers until a 1.0 release

Consider migrating from flake8/isort to ruff

ruff is a very fast Python linter written in Rust. It supports a large amount of rules and is being used by more and more high-profile Python projects.

ruff can replace and expand on the tools we are using right now, such as flake8 and isort.

I propose we migrate our current lint configuration to ruff.

Analysis of SDK internals (make sure they are "Pythonic")

As mentioned in #101, some of the SDK internals aren't very python-idiomatic. We should create some tasks to refactor this.

Definition of done:

  • Investigate all internal (non-public) artifacts/classes and evaluate for adherence to Python idioms
  • Create individual issues with specific goals for each

Python Asynchronous I/O Support

It would be nice to support Python's asyncio for provider and application authors because it's faster than multithreading for I/O, e.g. API request made to the backend server of the feature flags.

add pip compile to Github Workflows

To build the release we should follow our own best practices. The Github workflows should use pip tools to compile, create the requirements.txt file, and test from there.

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

This repository currently has no open or pending branches.

Detected dependencies

github-actions
.github/workflows/build.yml
  • actions/checkout v4
  • actions/setup-python v5
  • codecov/codecov-action v4.5.0
  • actions/checkout v4
  • actions/setup-python v5
  • pre-commit/action v3.0.1
  • actions/checkout v4
  • actions/setup-python v5
  • github/codeql-action v3
  • github/codeql-action v3
.github/workflows/lint-pr.yml
  • amannn/action-semantic-pull-request v5
.github/workflows/release.yml
  • googleapis/release-please-action v4
  • actions/checkout v4
  • python 3.12
pep621
pyproject.toml
  • coverage >=6.5
pre-commit
.pre-commit-config.yaml
  • astral-sh/ruff-pre-commit v0.6.5
  • pre-commit/pre-commit-hooks v4.6.0
  • pre-commit/mirrors-mypy v1.11.2

  • Check this box to trigger a request for Renovate to run again on this repository

Rename top-level package from open_feature to openfeature?

Should we rename the top-level package for the sdk from open_feature to openfeature?

The opentelemetry sdk does not use an underscore either, as is the case for most common Python packages which come from combined-word names (see matplotlib, numpy, pytorch, tensorflow)

This is of course a breaking change, but since we are revamping our whole package structure for this release, it's no more breaking than the rest of the changes that we are introducing.

I'm curious to hear your thoughts on this!

Add pip tools to the readme

To use the pip-compile dependency management flow we need to add the necessity for pip tools into the readme.md file

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.