cloudfoundry-client


Namecloudfoundry-client JSON
Version 1.38.0 PyPI version JSON
download
home_pagehttps://pypi.org/project/cloudfoundry-client/
SummaryA client library for CloudFoundry
upload_time2025-01-06 11:57:08
maintainerNone
docs_urlNone
authorBenjamin Einaudi
requires_python>=3.9
licenseNone
keywords cloudfoundry cf
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            Cloudfoundry python client
==========================
.. image:: https://img.shields.io/pypi/v/cloudfoundry-client.svg
    :target: https://pypi.python.org/pypi/cloudfoundry-client

.. image:: https://img.shields.io/github/license/antechrestos/cf-python-client.svg
    :target: https://raw.githubusercontent.com/antechrestos/cf-python-client/master/LICENSE

The cf-python-client repo contains a Python client library for Cloud Foundry. 

Installing
----------

Supported versions
~~~~~~~~~~~~~~~~~~

- Starting version ``1.11.0``, versions older than python ``3.6.0`` will not be supported anymore. This late version was released by the end 2016.
  For those that are still using python 2.7, it won't be supported by the end of 2020 and all library shall stop supporting it.
- Starting version ``1.25.0``, versions older than python ``3.7.0`` will not be supported anymore.
- Starting version ``1.36.0``, versions older than python ``3.8.0`` will not be supported anymore.

See `official documentation`_.

.. _`official documentation`: https://endoflife.date/python


From pip
~~~~~~~~

.. code-block:: bash

    $ pip install cloudfoundry-client

From sources
~~~~~~~~~~~~

To build the library run :

.. code-block:: bash

    $ python setup.py install


Run the client
--------------
To run the client, enter the following command :

.. code-block:: bash

    $ cloudfoundry-client

This will explains you how the client works. At first execution, it will ask you information about the platform you want to reach (url, login and so on).
Please note that your credentials won't be saved on your disk: only tokens will be kept for further use.

Use the client in your code
---------------------------
You may build the client and use it in your code

Client
~~~~~~
To instantiate the client, nothing easier

.. code-block:: python

    from cloudfoundry_client.client import CloudFoundryClient
    target_endpoint = 'https://somewhere.org'
    proxy = dict(http=os.environ.get('HTTP_PROXY', ''), https=os.environ.get('HTTPS_PROXY', ''))
    client = CloudFoundryClient(target_endpoint, proxy=proxy, verify=False)
    # init with user credentials
    client.init_with_user_credentials('login', 'password')
    # init with refresh token (that will retrieve a fresh access token)
    client.init_with_token('refresh-token')
    # init with access and refresh token (if the above method is not convenient)
    client.refresh_token = 'refresh-token'
    client._access_token = 'access-token'

You can also instantiate the client by reading the config file generated by `cf login`, which allows for authenticating via SSO and LDAP:

.. code-block:: python

    # init with endpoint & token from the cf cli config file
    from cloudfoundry_client.client import CloudFoundryClient

    # use the default file, i.e. ~/.cf/config.json
    client = CloudFoundryClient.build_from_cf_config()
    # or specify an alternative path
    # - other kwargs can be passed through to CloudFoundryClient instantiation
    client = CloudFoundryClient.build_from_cf_config(config_path="some/path/config.json", proxy=proxy, verify=False)

It can also be instantiated with oauth code flow if you possess a dedicated oauth application with its redirection

.. code-block:: python

    from flask import request
    from cloudfoundry_client.client import CloudFoundryClient
    target_endpoint = 'https://somewhere.org'
    proxy = dict(http=os.environ.get('HTTP_PROXY', ''), https=os.environ.get('HTTPS_PROXY', ''))
    client = CloudFoundryClient(target_endpoint, proxy=proxy, verify=False, client_id='my-client-id', client_secret='my-client-secret')

    @app.route('/login')
    def login():
        global client
        return redirect(client.generate_authorize_url('http://localhost:9999/code', '666'))

    @app.route('/code')
    def code():
        global client
        client.init_authorize_code_process('http://localhost:9999/code', request.args.get('code'))


And then you can use it as follows:

.. code-block:: python

    for organization in client.v2.organizations:
        print(organization['metadata']['guid'])

API V2
-------

Entities
~~~~~~~~
Entities returned by api V2 calls (*organization*, *space*, *app*..) are navigable ie you can call the method associated with the *xxx_url* entity attribute
(note that if the attribute's name ends with a list, it will be interpreted as a list of object. Other wise you will get a single entity).

.. code-block:: python

    for organization in client.v2.organizations:
        for space in organization.spaces(): # perform a GET on spaces_url attribute
            organization_reloaded = space.organization()  # perform a GET on organization_url attribute

Application object provides more methods such as
 - instances
 - stats
 - start
 - stop
 - summary

As instance, you can get all the summaries as follows:

Or else:

.. code-block:: python

    for app in client.v2.apps:
        print(app.summary())

Available managers
~~~~~~~~~~~~~~~~~~
So far the implemented managers that are available are:

- ``service_plans``
- ``service_plan_visibilities``
- ``service_instances``
- ``service_keys``
- ``service_bindings``
- ``service_brokers``
- ``apps``
- ``events``
- ``buildpacks``
- ``organizations``
- ``spaces``
- ``services``
- ``routes``
- ``shared_domains``
- ``private_domains``
- ``security_groups``

Note that even if, while navigating, you reach an entity manager that does not exist, the get will be performed and you will get the expected entities.
For example, event entity manager is not yet implemented but you can do

.. code-block:: python

    for app in client.v2.apps:
        for event in app.events():
            handle_event_object()

All managers provide the following methods:

- ``list(**kwargs)``: return an *iterator* on entities, according to the given filtered parameters
- ``get_first(**kwargs)``: return the first matching entity according to the given parameters. Returns ```None`` if none returned
- ``get``: perform a **GET** on the entity. If the entity cannot be find it will raise an exception due to http *NOT FOUND* response status
- ``__iter__``: iteration on the manager itself. Alias for a no-filter list
- ``__getitem__``: alias for the ``get`` operation
- ``_create``: the create operation. Since it is a generic operation (only takes a *dict* object), this operation is protected
- ``_update``: the update operation. Since it is a generic operation (only takes a the resource id and a *dict* object), this operation is protected
- ``_remove``: the delete operation. This operation is maintained protected.

.. code-block:: python

    # Assume you have an organization named `test-org` with a guid of `test-org-guid`
    org_get = client.v2.organizations.get('test-org-guid')
    org_get_first = client.v2.organizations.get_first(**{'name': 'test-org'})
    org_from_list = list(client.v2.organizations.list(**{'name': 'test-org'}))[0]
    assert org_get == org_get_first == org_from_list

    # You can also specify multiple values for a query parameter.
    for organization in client.v2.organizations.list(**{'name': ['org1', 'org2']}):
        print(organization['metadata']['guid'])

    # Order and Paging parameters are also supported.
    query = {
    	'order-by': 'name',
    	'order-direction': 'desc',
    	'results-per-page': 100
    }
    for organization in client.v2.organizations.list(**query):
        print(organization['entity']['name'])

API V3
------

Entities
~~~~~~~~

Entities returned by API V3 calls transcripts links by providing a call on the object with the name of the link itself.
Let's explain it with the next code

.. code-block:: python

  for app in client.v3.apps.list(space_guids='space_guid'):
    for task in app.tasks():
        print('Task %s' % task['guid'])
    app.stop()
    space = app.space()

Another example:

.. code-block:: python

    app = client.v3.apps['app-guid']
    for task in app.tasks():
        task.cancel()
    for task in client.v3.tasks.list(app_guids=['app-guid-1', 'app-guid-2']):
        task.cancel()

When supported by the API, parent entities can be included in a single call. The included entities replace the links mentioned above.
The following code snippet issues three requests to the API in order to get app, space and organization data:

.. code-block:: python

  app = client.v3.apps.get("app-guid")
  print("App name: %s" % app["name"])
  space = app.space()
  print("Space name: %s" % space["name"])
  org = space.organization()
  print("Org name: %s" % org["name"])

By changing the first line only, a single request fetches all the data. The navigation from app to space and space to organization remains unchanged.

.. code-block:: python

  app = client.v3.apps.get("app-guid", include="space.organization")

Available managers on API V3 are:

- ``apps``
- ``buildpacks``
- ``domains``
- ``feature_flags``
- ``isolation_segments``
- ``jobs``
- ``organizations``
- ``organization_quotas``
- ``processes``
- ``roles``
- ``security_groups``
- ``service_brokers``
- ``service_credential_bindings``
- ``service_instances``
- ``service_offerings``
- ``service_plans``
- ``spaces``
- ``tasks``

The managers provide the same methods as the V2 managers with the following differences:

- ``get(**kwargs)``: supports keyword arguments that are passed on to the API, e.g. "include"


Networking
----------

policy server
~~~~~~~~~~~~~

At the moment we have only the network policies implemented

.. code-block:: python

  for policy in client.network.v1.external.policies.list():
    print('destination protocol = {}'.format(policy['destination']['protocol']))
    print('destination from port = {}'.format(policy['destination']['ports']['start']))
    print('destination to port = {}'.format(policy['destination']['ports']['end']))


Available managers on API V3 are:

- ``policy``

This manager provides:

- ``list(**kwargs)``: return an *iterator* on entities, according to the given filtered parameters
- ``__iter__``: iteration on the manager itself. Alias for a no-filter list
- ``_create``: the create operation. Since it is a generic operation (only takes a *dict* object), this operation is protected
- ``_remove``: the delete operation. This operation is maintained protected.


Application logs
----------------

Recent logs of an application can be get as follows:

.. code-block:: python

    app = client.v2.apps['app-guid']
    for log in app.recent_logs():
        print(log)


Logs can also be streamed using a websocket as follows:

.. code-block:: python

    app = client.v2.apps['app-guid']
    for log in app.stream_logs():
        # read message infinitely (use break to exit... it will close the underlying websocket)
        print(log)
    # or
    for log in client.doppler.stream_logs('app-guid'):
        # read message infinitely (use break to exit... it will close the underlying websocket)
        print(log)

..

Logs can also be streamed directly from RLP Gateway:

.. code-block:: python

    import asyncio
    from cloudfoundry_client.client import CloudFoundryClient

    target_endpoint = 'https://somewhere.org'
    proxy = dict(http=os.environ.get('HTTP_PROXY', ''), https=os.environ.get('HTTPS_PROXY', ''))
    rlp_client = CloudFoundryClient(target_endpoint, client_id='client_id', client_secret='client_secret', verify=False)
    # init with client credentials
    rlp_client.init_with_client_credentials()

    async def get_logs_for_app(rlp_client, app_guid):
        async for log in rlp_client.rlpgateway.stream_logs(app_guid,
                                                           params={'counter': '', 'gauge': ''},
                                                           headers={'User-Agent': 'cf-python-client'})):
            print(log)

    loop = asyncio.get_event_loop()
    loop.create_task(get_logs_for_app(rlp_client, "app_guid"))
    loop.run_forever()
    loop.close()
..

Command Line Interface
----------------------

The client comes with a command line interface. Run ``cloudfoundry-client`` command. At first execution, it will ask you information about the target platform and your credential (do not worry they are not saved). After that you may have a help by running ``cloudfoundry-client -h``

Operations (experimental)
-------------------------

For now the only operation that is implemented is the push one.

.. code-block:: python

    from cloudfoundry_client.operations.push.push import PushOperation
    operation = PushOperation(client)
    operation.push(client.v2.spaces.get_first(name='My Space')['metadata']['guid'], path)


Issues and contributions
------------------------

Please submit issue/pull request.

You can run tests by doing so. In the project directory:

.. code-block:: bash

    $ export PYTHONPATH=main
    $ python -m unittest discover test
    # or even
    $ python setup.py test


            

Raw data

            {
    "_id": null,
    "home_page": "https://pypi.org/project/cloudfoundry-client/",
    "name": "cloudfoundry-client",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.9",
    "maintainer_email": null,
    "keywords": "cloudfoundry, cf",
    "author": "Benjamin Einaudi",
    "author_email": "antechrestos@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/d9/4e/944c6da9246880b46376d81079a70f5b4007ca9acd2dd256f2b135b72958/cloudfoundry_client-1.38.0.tar.gz",
    "platform": null,
    "description": "Cloudfoundry python client\n==========================\n.. image:: https://img.shields.io/pypi/v/cloudfoundry-client.svg\n    :target: https://pypi.python.org/pypi/cloudfoundry-client\n\n.. image:: https://img.shields.io/github/license/antechrestos/cf-python-client.svg\n    :target: https://raw.githubusercontent.com/antechrestos/cf-python-client/master/LICENSE\n\nThe cf-python-client repo contains a Python client library for Cloud Foundry. \n\nInstalling\n----------\n\nSupported versions\n~~~~~~~~~~~~~~~~~~\n\n- Starting version ``1.11.0``, versions older than python ``3.6.0`` will not be supported anymore. This late version was released by the end 2016.\n  For those that are still using python 2.7, it won't be supported by the end of 2020 and all library shall stop supporting it.\n- Starting version ``1.25.0``, versions older than python ``3.7.0`` will not be supported anymore.\n- Starting version ``1.36.0``, versions older than python ``3.8.0`` will not be supported anymore.\n\nSee `official documentation`_.\n\n.. _`official documentation`: https://endoflife.date/python\n\n\nFrom pip\n~~~~~~~~\n\n.. code-block:: bash\n\n    $ pip install cloudfoundry-client\n\nFrom sources\n~~~~~~~~~~~~\n\nTo build the library run :\n\n.. code-block:: bash\n\n    $ python setup.py install\n\n\nRun the client\n--------------\nTo run the client, enter the following command :\n\n.. code-block:: bash\n\n    $ cloudfoundry-client\n\nThis will explains you how the client works. At first execution, it will ask you information about the platform you want to reach (url, login and so on).\nPlease note that your credentials won't be saved on your disk: only tokens will be kept for further use.\n\nUse the client in your code\n---------------------------\nYou may build the client and use it in your code\n\nClient\n~~~~~~\nTo instantiate the client, nothing easier\n\n.. code-block:: python\n\n    from cloudfoundry_client.client import CloudFoundryClient\n    target_endpoint = 'https://somewhere.org'\n    proxy = dict(http=os.environ.get('HTTP_PROXY', ''), https=os.environ.get('HTTPS_PROXY', ''))\n    client = CloudFoundryClient(target_endpoint, proxy=proxy, verify=False)\n    # init with user credentials\n    client.init_with_user_credentials('login', 'password')\n    # init with refresh token (that will retrieve a fresh access token)\n    client.init_with_token('refresh-token')\n    # init with access and refresh token (if the above method is not convenient)\n    client.refresh_token = 'refresh-token'\n    client._access_token = 'access-token'\n\nYou can also instantiate the client by reading the config file generated by `cf login`, which allows for authenticating via SSO and LDAP:\n\n.. code-block:: python\n\n    # init with endpoint & token from the cf cli config file\n    from cloudfoundry_client.client import CloudFoundryClient\n\n    # use the default file, i.e. ~/.cf/config.json\n    client = CloudFoundryClient.build_from_cf_config()\n    # or specify an alternative path\n    # - other kwargs can be passed through to CloudFoundryClient instantiation\n    client = CloudFoundryClient.build_from_cf_config(config_path=\"some/path/config.json\", proxy=proxy, verify=False)\n\nIt can also be instantiated with oauth code flow if you possess a dedicated oauth application with its redirection\n\n.. code-block:: python\n\n    from flask import request\n    from cloudfoundry_client.client import CloudFoundryClient\n    target_endpoint = 'https://somewhere.org'\n    proxy = dict(http=os.environ.get('HTTP_PROXY', ''), https=os.environ.get('HTTPS_PROXY', ''))\n    client = CloudFoundryClient(target_endpoint, proxy=proxy, verify=False, client_id='my-client-id', client_secret='my-client-secret')\n\n    @app.route('/login')\n    def login():\n        global client\n        return redirect(client.generate_authorize_url('http://localhost:9999/code', '666'))\n\n    @app.route('/code')\n    def code():\n        global client\n        client.init_authorize_code_process('http://localhost:9999/code', request.args.get('code'))\n\n\nAnd then you can use it as follows:\n\n.. code-block:: python\n\n    for organization in client.v2.organizations:\n        print(organization['metadata']['guid'])\n\nAPI V2\n-------\n\nEntities\n~~~~~~~~\nEntities returned by api V2 calls (*organization*, *space*, *app*..) are navigable ie you can call the method associated with the *xxx_url* entity attribute\n(note that if the attribute's name ends with a list, it will be interpreted as a list of object. Other wise you will get a single entity).\n\n.. code-block:: python\n\n    for organization in client.v2.organizations:\n        for space in organization.spaces(): # perform a GET on spaces_url attribute\n            organization_reloaded = space.organization()  # perform a GET on organization_url attribute\n\nApplication object provides more methods such as\n - instances\n - stats\n - start\n - stop\n - summary\n\nAs instance, you can get all the summaries as follows:\n\nOr else:\n\n.. code-block:: python\n\n    for app in client.v2.apps:\n        print(app.summary())\n\nAvailable managers\n~~~~~~~~~~~~~~~~~~\nSo far the implemented managers that are available are:\n\n- ``service_plans``\n- ``service_plan_visibilities``\n- ``service_instances``\n- ``service_keys``\n- ``service_bindings``\n- ``service_brokers``\n- ``apps``\n- ``events``\n- ``buildpacks``\n- ``organizations``\n- ``spaces``\n- ``services``\n- ``routes``\n- ``shared_domains``\n- ``private_domains``\n- ``security_groups``\n\nNote that even if, while navigating, you reach an entity manager that does not exist, the get will be performed and you will get the expected entities.\nFor example, event entity manager is not yet implemented but you can do\n\n.. code-block:: python\n\n    for app in client.v2.apps:\n        for event in app.events():\n            handle_event_object()\n\nAll managers provide the following methods:\n\n- ``list(**kwargs)``: return an *iterator* on entities, according to the given filtered parameters\n- ``get_first(**kwargs)``: return the first matching entity according to the given parameters. Returns ```None`` if none returned\n- ``get``: perform a **GET** on the entity. If the entity cannot be find it will raise an exception due to http *NOT FOUND* response status\n- ``__iter__``: iteration on the manager itself. Alias for a no-filter list\n- ``__getitem__``: alias for the ``get`` operation\n- ``_create``: the create operation. Since it is a generic operation (only takes a *dict* object), this operation is protected\n- ``_update``: the update operation. Since it is a generic operation (only takes a the resource id and a *dict* object), this operation is protected\n- ``_remove``: the delete operation. This operation is maintained protected.\n\n.. code-block:: python\n\n    # Assume you have an organization named `test-org` with a guid of `test-org-guid`\n    org_get = client.v2.organizations.get('test-org-guid')\n    org_get_first = client.v2.organizations.get_first(**{'name': 'test-org'})\n    org_from_list = list(client.v2.organizations.list(**{'name': 'test-org'}))[0]\n    assert org_get == org_get_first == org_from_list\n\n    # You can also specify multiple values for a query parameter.\n    for organization in client.v2.organizations.list(**{'name': ['org1', 'org2']}):\n        print(organization['metadata']['guid'])\n\n    # Order and Paging parameters are also supported.\n    query = {\n    \t'order-by': 'name',\n    \t'order-direction': 'desc',\n    \t'results-per-page': 100\n    }\n    for organization in client.v2.organizations.list(**query):\n        print(organization['entity']['name'])\n\nAPI V3\n------\n\nEntities\n~~~~~~~~\n\nEntities returned by API V3 calls transcripts links by providing a call on the object with the name of the link itself.\nLet's explain it with the next code\n\n.. code-block:: python\n\n  for app in client.v3.apps.list(space_guids='space_guid'):\n    for task in app.tasks():\n        print('Task %s' % task['guid'])\n    app.stop()\n    space = app.space()\n\nAnother example:\n\n.. code-block:: python\n\n    app = client.v3.apps['app-guid']\n    for task in app.tasks():\n        task.cancel()\n    for task in client.v3.tasks.list(app_guids=['app-guid-1', 'app-guid-2']):\n        task.cancel()\n\nWhen supported by the API, parent entities can be included in a single call. The included entities replace the links mentioned above.\nThe following code snippet issues three requests to the API in order to get app, space and organization data:\n\n.. code-block:: python\n\n  app = client.v3.apps.get(\"app-guid\")\n  print(\"App name: %s\" % app[\"name\"])\n  space = app.space()\n  print(\"Space name: %s\" % space[\"name\"])\n  org = space.organization()\n  print(\"Org name: %s\" % org[\"name\"])\n\nBy changing the first line only, a single request fetches all the data. The navigation from app to space and space to organization remains unchanged.\n\n.. code-block:: python\n\n  app = client.v3.apps.get(\"app-guid\", include=\"space.organization\")\n\nAvailable managers on API V3 are:\n\n- ``apps``\n- ``buildpacks``\n- ``domains``\n- ``feature_flags``\n- ``isolation_segments``\n- ``jobs``\n- ``organizations``\n- ``organization_quotas``\n- ``processes``\n- ``roles``\n- ``security_groups``\n- ``service_brokers``\n- ``service_credential_bindings``\n- ``service_instances``\n- ``service_offerings``\n- ``service_plans``\n- ``spaces``\n- ``tasks``\n\nThe managers provide the same methods as the V2 managers with the following differences:\n\n- ``get(**kwargs)``: supports keyword arguments that are passed on to the API, e.g. \"include\"\n\n\nNetworking\n----------\n\npolicy server\n~~~~~~~~~~~~~\n\nAt the moment we have only the network policies implemented\n\n.. code-block:: python\n\n  for policy in client.network.v1.external.policies.list():\n    print('destination protocol = {}'.format(policy['destination']['protocol']))\n    print('destination from port = {}'.format(policy['destination']['ports']['start']))\n    print('destination to port = {}'.format(policy['destination']['ports']['end']))\n\n\nAvailable managers on API V3 are:\n\n- ``policy``\n\nThis manager provides:\n\n- ``list(**kwargs)``: return an *iterator* on entities, according to the given filtered parameters\n- ``__iter__``: iteration on the manager itself. Alias for a no-filter list\n- ``_create``: the create operation. Since it is a generic operation (only takes a *dict* object), this operation is protected\n- ``_remove``: the delete operation. This operation is maintained protected.\n\n\nApplication logs\n----------------\n\nRecent logs of an application can be get as follows:\n\n.. code-block:: python\n\n    app = client.v2.apps['app-guid']\n    for log in app.recent_logs():\n        print(log)\n\n\nLogs can also be streamed using a websocket as follows:\n\n.. code-block:: python\n\n    app = client.v2.apps['app-guid']\n    for log in app.stream_logs():\n        # read message infinitely (use break to exit... it will close the underlying websocket)\n        print(log)\n    # or\n    for log in client.doppler.stream_logs('app-guid'):\n        # read message infinitely (use break to exit... it will close the underlying websocket)\n        print(log)\n\n..\n\nLogs can also be streamed directly from RLP Gateway:\n\n.. code-block:: python\n\n    import asyncio\n    from cloudfoundry_client.client import CloudFoundryClient\n\n    target_endpoint = 'https://somewhere.org'\n    proxy = dict(http=os.environ.get('HTTP_PROXY', ''), https=os.environ.get('HTTPS_PROXY', ''))\n    rlp_client = CloudFoundryClient(target_endpoint, client_id='client_id', client_secret='client_secret', verify=False)\n    # init with client credentials\n    rlp_client.init_with_client_credentials()\n\n    async def get_logs_for_app(rlp_client, app_guid):\n        async for log in rlp_client.rlpgateway.stream_logs(app_guid,\n                                                           params={'counter': '', 'gauge': ''},\n                                                           headers={'User-Agent': 'cf-python-client'})):\n            print(log)\n\n    loop = asyncio.get_event_loop()\n    loop.create_task(get_logs_for_app(rlp_client, \"app_guid\"))\n    loop.run_forever()\n    loop.close()\n..\n\nCommand Line Interface\n----------------------\n\nThe client comes with a command line interface. Run ``cloudfoundry-client`` command. At first execution, it will ask you information about the target platform and your credential (do not worry they are not saved). After that you may have a help by running ``cloudfoundry-client -h``\n\nOperations (experimental)\n-------------------------\n\nFor now the only operation that is implemented is the push one.\n\n.. code-block:: python\n\n    from cloudfoundry_client.operations.push.push import PushOperation\n    operation = PushOperation(client)\n    operation.push(client.v2.spaces.get_first(name='My Space')['metadata']['guid'], path)\n\n\nIssues and contributions\n------------------------\n\nPlease submit issue/pull request.\n\nYou can run tests by doing so. In the project directory:\n\n.. code-block:: bash\n\n    $ export PYTHONPATH=main\n    $ python -m unittest discover test\n    # or even\n    $ python setup.py test\n\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "A client library for CloudFoundry",
    "version": "1.38.0",
    "project_urls": {
        "Documentation": "https://pypi.org/project/cloudfoundry-client/",
        "Homepage": "https://pypi.org/project/cloudfoundry-client/",
        "Repository": "https://github.com/cloudfoundry-community/cf-python-client"
    },
    "split_keywords": [
        "cloudfoundry",
        " cf"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "e3b9273e58efc1bd09e52f2b2de42141a9d288fcd6df045ffbaac557d52dbd8c",
                "md5": "e5674529406567f975efec7cb5495eaa",
                "sha256": "5ae88a4c0d6dfa4be4a89be7bafff14136268b8d910760895f1edf6e8324e5b1"
            },
            "downloads": -1,
            "filename": "cloudfoundry_client-1.38.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "e5674529406567f975efec7cb5495eaa",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.9",
            "size": 73342,
            "upload_time": "2025-01-06T11:57:04",
            "upload_time_iso_8601": "2025-01-06T11:57:04.831547Z",
            "url": "https://files.pythonhosted.org/packages/e3/b9/273e58efc1bd09e52f2b2de42141a9d288fcd6df045ffbaac557d52dbd8c/cloudfoundry_client-1.38.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "d94e944c6da9246880b46376d81079a70f5b4007ca9acd2dd256f2b135b72958",
                "md5": "70886d3f88b467a67becc33ebc3f2811",
                "sha256": "9513c6ee2b256c21bf56e2a30a230254cb34d8d50863efd96f8068ca2ded927c"
            },
            "downloads": -1,
            "filename": "cloudfoundry_client-1.38.0.tar.gz",
            "has_sig": false,
            "md5_digest": "70886d3f88b467a67becc33ebc3f2811",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9",
            "size": 49256,
            "upload_time": "2025-01-06T11:57:08",
            "upload_time_iso_8601": "2025-01-06T11:57:08.054806Z",
            "url": "https://files.pythonhosted.org/packages/d9/4e/944c6da9246880b46376d81079a70f5b4007ca9acd2dd256f2b135b72958/cloudfoundry_client-1.38.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-01-06 11:57:08",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "cloudfoundry-community",
    "github_project": "cf-python-client",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "cloudfoundry-client"
}
        
Elapsed time: 0.41673s