{ "info": { "author": "John-Paul Jorissen", "author_email": "jjorissen52@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: Microsoft :: Windows :: Windows 10", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3.6", "Topic :: Office/Business" ], "description": "\n ::\n\n\n ########:: ##:::: ##: ##::::::: ##::::::: ##:::: ##:: #######:: ########:: ##::: ##:::::::::::::\n ##.... ##: ##:::: ##: ##::::::: ##::::::: ##:::: ##:'##.... ##: ##.... ##: ###:: ##:::::::::::::\n ##:::: ##: ##:::: ##: ##::::::: ##::::::: ##:::: ##: ##:::: ##: ##:::: ##: ####: ##:::::::::::::\n ########:: ##:::: ##: ##::::::: ##::::::: #########: ##:::: ##: ########:: ## ## ##:::::::::::::\n ##.... ##: ##:::: ##: ##::::::: ##::::::: ##.... ##: ##:::: ##: ##.. ##::: ##. ####:::::::::::::\n ##:::: ##: ##:::: ##: ##::::::: ##::::::: ##:::: ##: ##:::: ##: ##::. ##:: ##:. ###:::::::::::::\n ########::. #######:: ########: ########: ##:::: ##:. #######:: ##:::. ##: ##::. ##:::::::::::::\n ........::::.......:::........::........::..:::::..:::.......:::..:::::..::..::::..::::::::::::::\n :::::::::'####:'##::: ##:'########:'########:'########::'########::::'###:::::'######::'########:\n :::::::::. ##:: ###:: ##:... ##..:: ##.....:: ##.... ##: ##.....::::'## ##:::'##... ##: ##.....::\n :::::::::: ##:: ####: ##:::: ##:::: ##::::::: ##:::: ##: ##::::::::'##:. ##:: ##:::..:: ##:::::::\n :::::::::: ##:: ## ## ##:::: ##:::: ######::: ########:: ######:::'##:::. ##: ##::::::: ######:::\n :::::::::: ##:: ##. ####:::: ##:::: ##...:::: ##.. ##::: ##...:::: #########: ##::::::: ##...::::\n :::::::::: ##:: ##:. ###:::: ##:::: ##::::::: ##::. ##:: ##::::::: ##.... ##: ##::: ##: ##:::::::\n ::::::::::####: ##::. ##:::: ##:::: ########: ##:::. ##: ##::::::: ##:::: ##:. ######:: ########:\n ::::::::::....::..::::..:::::..:::::........::..:::::..::..::::::::..:::::..:::......:::........::\n\nDescription\n===========\n\nThis package facilitates the usage of Bullhorn's developer API. This package has full documentation available at ``__ .\n\nFeatures\n--------\n\n- Handles Authorization\n\n - Stored Credentials Optional\n\n- Handles Tokens\n\n - Granting\n - Storing\n - Auto Refresh Expired Tokens\n\n- Facilitates Simple Concurrency\n- Works in Windows (Please no flash photography)\n\n\nEnvironment Setup\n=================\n\nLinux\n-----\n\nCreate environment using anaconda or whatever and activate it:\n\n.. code:: python\n\n conda create -n bullhorn3.6\n source activate bullhorn3.6\n pip install -r /path/to/project_root/requirements.txt\n\nWindows (Anaconda)\n==================\n\nSame as above, but you will need to perform\n\n.. code:: python\n\n conda install psycopg2\n conda install sqlalchemy\n\nafterwards, as there are some dependencies that Anaconda has to work out\nto make these packages work on Windows. I highly recommend you use\nAnaconda in windows, as it will handle all the nasty c bits that\nnumerous python packages require.\n\n ## Configuration\n\nThere needs to be a file named ``bullhorn_interface.conf`` that looks\nlike this somewhere on your system:\n\n.. code:: python\n\n [bullhorn_interface]\n TOKEN_HANDLER = [pick from 'live', 'pg', or 'sqlite']\n CLIENT_ID = client_id\n CLIENT_SECRET = client_secret\n BULLHORN_USERNAME = username\n BULLHORN_PASSWORD = password\n EMAIL_ADDRESS = email@email.com\n EMAIL_PASSWORD = password\n DB_NAME = bullhorn_box\n DB_HOST = localhost\n DB_USER = db_user\n DB_PASSWORD = password\n\nIf this file lives in your working directory you are good to go. If not,\nyou will need to set an environment variable to the full path of this\nfile. Note that you can leave each of these lines blank if you are not\ncomfortable storing items in plaintext, but none of the test will pass\nif vital items are left blank. See `here <#no_plaintext>`__ about how to\nuse the interface without storing credentials in plain text.\n\nLinux\n=====\n\n.. code:: python\n\n export INTERFACE_CONF_FILE=/home/jjorissen/interface_secrets.conf\n\nWindows\n=======\n\n.. code:: python\n\n set INTERFACE_CONF_FILE=/full/path/to/bullhorn_secrets.conf\n\nPython\n\n.. code:: python\n\n import os\n os.environ['INTERFACE_CONF_FILE'] = '/home/jjorissen/interface_secrets.conf'\n\nTo test your current configuration you can do:\n\n.. code:: python\n\n # this cannot be run in jupyter notebooks, sadly.\n from bullhorn_interface import tests\n tests.run()\n\nIf you want to run a full coverage test (for even the features you\naren't configured for) you can set the below environment variable first.\n\n.. code:: python\n\n export TEST_FULL_COVERAGE=1 # it's actually not quite full coverage, sorry.\n\nDevelopers, you can run the below to test the coverage.\n\n.. code:: python\n\n sudo apt-get install coverage\n coverage run -m unittest discover -s bullhorn_interface/\n #inline summary\n coverage report -m\n # generate browser navigable summary\n coverage html\n\n\n\n\nUsing Postgres or SQLite\n========================\n\nDatabase Setup\n-------------------\n\nNote: If you are using PG, your ``DB_USER`` must have access to the 'postgres' database on your postgreSQL server, and must have sufficient permissions to create and edit databases.\n\n\nTo create a database to house your tokens:\n\n.. code:: python\n\n from bullhorn_interface.api import tokenbox\n tokenbox.create_database() \n\n\n.. parsed-literal::\n\n bullhorn_box created successfully.\n\n\nIf you wish to drop that database for some reason:\n\n.. code:: python\n\n tokenbox.destroy_database()\n\n\n.. parsed-literal::\n\n Database named bullhorn_box will be destroyed in 5...4...3...2...1...0\n bullhorn_box dropped successfully.\n\n\nIt's that easy. The necessary tables will be created automatically when\nthe tokens are generated for the first time, so don't sweat anything!\nFor more information on using ``tokenbox``, visit the\n`repo `__\n\nInterface Explanation\n===================\n\n``bullhorn_interface`` interacts will Bullhorn's\nAPI using ``Interface`` objects.\n\n- ``LiveInterface`` keeps tokens on itself. These guys should always be created as ``independent``, as ``LiveInterface`` objects are capable of refreshing expired tokens only for themselves.\n- ``StoredInterface`` keeps tokens on itself and also checks tokens in the database before allowing a refresh to happen. This allows you to use the same token among many interfaces in case you need to have many running at once. \\* Bullhorn doesn't seem to mind if you have numerous API logins running simultaneously, so there isn't much utility to the ``StoredInterface``. However, in the case where you are creating new ``Interface`` objects frequently, using an ```independent`` stored interface will keep you from having to wait on unnecessary ``login()`` calls.\n\nUsing LiveInterface\n====================\n\n\nGenerate Login Token\n------------------------\n\n.. code:: python\n\n from bullhorn_interface import api\n interface = api.LiveInterface(username=api.BULLHORN_USERNAME, password=api.BULLHORN_PASSWORD)\n interface.login()\n\n\n.. parsed-literal::\n\n New Login Token\n\n\nGenerate API Token\n-------------------\n\nOnce you've been granted a login token, you can get a token and url for the rest API.\n\n.. code:: python\n\n interface.get_api_token()\n\n\n.. parsed-literal::\n\n New Access Token\n\n\nMake API Calls\n-------------------\n\n.. code:: python\n\n import pandas\n # equivalent to query=\"lastName:Jorissen AND firstName:John-Paul\"\n df = pandas.DataFrame(interface.api_search(entity='Candidate', lastName=\"Jorissen\", firstName=\"John-Paul\")['data'])\n # df = pandas.DataFrame(interface.api_search(entity='Candidate', query=\"lastName:Jorissen AND firstName:John-Paul\")['data'])\n df[['lastName', 'firstName']].head(2)\n\n\n.. parsed-literal::\n\n New Login Token\n New Access Token\n Refreshing API Token\n\n\n\n\n.. raw:: html\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
lastNamefirstName
0JorissenJohn-Paul
1JorissenJohn-Paul
\n
\n\n\n\nIf you can get a candidate by name like above, everything is setup\nproperly.\n\nUsing StoredInterface\n=====================\n\nIf you for `some reason <#storedinterface_reasons>`__ need (or want) to\nkeep your tokens stored in a database, you can use the stored interface.\n\n.. code:: python\n\n interface = api.StoredInterface(username=api.BULLHORN_USERNAME, password=api.BULLHORN_PASSWORD)\n\nYou interact with everything the same way as the ``LiveInterface``\nsetup.\n\n.. code:: python\n\n interface.login()\n interface.get_api_token()\n # there is never a reason to manually invoke refresh_token(); api_call() will handle expired tokens for you. \n interface.refresh_token()\n df = pandas.DataFrame(interface.api_search(entity='Candidate', lastName=\"Jorissen\", firstName=\"John-Paul\")['data'])\n\n\n.. parsed-literal::\n\n New Login Token\n New Access Token\n\n\n.. code:: python\n\n df[['lastName', 'firstName']].head(2)\n\n\n\n\n.. raw:: html\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
lastNamefirstName
0JorissenJohn-Paul
1JorissenJohn-Paul
\n
\n\n\n\n There is one difference here, however. You can make your\n``StoredInterface`` objects independent. This means that they will not\nlogin or refresh tokens on their own; they will instead be relying on a\nlead ``StoredInterface`` to keep tokens fresh. For a demonstration run 1\nand 2 in separate python command prompts.\n\n.. code:: python\n\n from bullhorn_interface import api\n first, last = \"John-Paul\", \"Jorissen\"\n qs = f\"firstName:{first} AND lastName:{last}\"\n lead_interface = api.StoredInterface(username=api.BULLHORN_USERNAME, password=api.BULLHORN_PASSWORD)\n dependent_interface = api.StoredInterface(username=api.BULLHORN_USERNAME, password=api.BULLHORN_PASSWORD, \n independent=False)\n lead_interface.login()\n lead_interface.get_api_token()\n # using the tokens that lead_interface aquired\n dependent_interface.api_call(query=qs)\n # forcing the dependent interface to think the token on its person has expired\n dependent_interface.login_token['expiry'] = 0\n # the interface will now check itself and find that it's token has expired. after the first failure, it will \n # check the database to see if an independent interface has put in a token that has not expired.\n dependent_interface.api_call(query=qs)['data'][0]\n\n\n.. parsed-literal::\n\n New Login Token\n New Access Token\n Token Expired. Attempt 1/10 failed.\n\n\n\n\n.. parsed-literal::\n\n {'_score': 1.0,\n 'comments': '',\n 'firstName': 'John-Paul',\n 'id': 425082,\n 'lastName': 'Jorissen',\n 'middleName': None,\n 'notes': {'data': [], 'total': 0}}\n\n\n\nAvoiding Plaintext Passwords\n==============================\n\nIf you are a bit squeamish about storing your Bullhorn login credentials\nin plaintext somewhere on your filesystem there is a workaround for you.\n\n.. code:: python\n\n import os\n os.environ['INTERFACE_CONF_FILE'] = '/home/jjorissen/bullhorn_secrets.conf'\n from bullhorn_interface import api\n # don't give the interface your password in the config file (leave that field blank)\n interface = api.LiveInterface(username=\"\", password=\"\")\n # run login and get the url that will generate a login code for you. YOU MUST RUN IT YOURSELF; VISITING\n # THE URL FROM THIS TUTORIAL WILL NOT WORK FOR YOU.\n interface.login()\n\n::\n\n Credentials not provided. Provide a username/password combination or follow the procedure below: \n Paste this URL into browser https://auth.bullhornstaffing.com/oauth/authorize?client_id=YOUCLIENTID&response_type=code \n Redirect URL will look like this: http://www.bullhorn.com/?code=YOUR%CODE%WILL%BE%RIGHT%HERE&client_id=YOURCLIENTID.\n\n.. code:: python\n\n # you can only login with this code once.\n interface.login(code=\"YOUR%CODE%WILL%BE%RIGHT%HERE\")\n\n\n.. parsed-literal::\n\n New Login Token\n\n\nYou can also avoiding storing any other sensitive information in\nplaintext by omitting them from your configurations (leave the key\nempty) file and manually adding it to the ``Interface`` and\n``api.tokenbox`` like shown below:\n\n.. code:: python\n\n from tokenbox import TokenBox\n api.tokenbox = TokenBox('username', 'password', 'db_name', api.metadata, db_host='localhost', \n use_sqlite=True, **api.table_definitions)\n interface.client_id = \"I%am%your%client%ID\"\n interface.client_secret = \"I%am%your%client%secret\"\n interface.login()\n\nAPI Guides\n==============\n\nNow with your interfaces in order, you can make API calls. This will all\nbe done with ``interface.api_call`` and numerous other helper methods.\nYou'll need to look over the Bullhorn API Reference Material if you\nhaven't already to familiarize yourself with the entities and how they\nrelated to one another.\n\n- `Bullhorn API Reference `__\n- `Bullhorn Entity\n Guide `__\n- `bullhorn_interface API documentation `__\n\nGet Candidate IDs (and comments) by first and last name\n\n.. code:: python\n\n first_name, last_name = \"John-Paul\", \"Jorissen\"\n \n def get_candidate_id(first_name, last_name):\n return interface.api_call(command=\"search\", entity=\"Candidate\", select_fields=[\"id\", \"comments\"],\n query=f\"firstName:{first_name} AND lastName:{last_name}\")\n \n candidate = get_candidate_id(first_name, last_name)['data']\n print(list(filter(lambda x: x['id'] == 425084, candidate)))\n\n\n.. parsed-literal::\n\n [{'id': 425084, 'comments': 'I am the old comment', '_score': 1.0}]\n\n\nUpdate a Candidate's comments\n\n.. code:: python\n\n candidate_id = 425084\n comments = 'I am the new comment'\n body = {\"comments\": comments}\n interface.api_call(command=\"entity\", entity=\"Candidate\", entity_id=candidate_id, body=body, method=\"UPDATE\")\n\n\n\n\n.. parsed-literal::\n\n {'changeType': 'UPDATE',\n 'changedEntityId': 425084,\n 'changedEntityType': 'Candidate',\n 'data': {'comments': 'I am the new comment'}}\n\n\n\n.. code:: python\n\n print(list(filter(lambda x: x['id'] == 425084, get_candidate_id(first_name, last_name)['data'])))\n\n\n.. parsed-literal::\n\n [{'id': 425084, 'comments': 'I am the new comment', '_score': 1.0}]\n\n\nQuestions\n=========\n\nFeel free to contact me with questions and suggestions of improvements.\nContributions are greatly appreciated.\n\n`jjorissen52@gmail.com `__\n", "description_content_type": null, "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/jjorissen52/bullhorn_interface", "keywords": "Bullhorn Python API RESTAPI", "license": "Apache", "maintainer": "", "maintainer_email": "", "name": "bullhorn_interface", "package_url": "https://pypi.org/project/bullhorn_interface/", "platform": "", "project_url": "https://pypi.org/project/bullhorn_interface/", "project_urls": { "Homepage": "https://github.com/jjorissen52/bullhorn_interface" }, "release_url": "https://pypi.org/project/bullhorn_interface/2.0.14.dev0/", "requires_dist": null, "requires_python": "", "summary": "A simple Python package to facilitate interactions with the Bullhorn REST API", "version": "2.0.14.dev0" }, "last_serial": 3554559, "releases": { "1.0.0.dev0": [ { "comment_text": "", "digests": { "md5": "cfa2e92bf00f56b01b65da549332d78a", "sha256": "54f02c61e0b44a4d8b4de4f80ba6ee87efd4695d08b0cda23327cb82ec182748" }, "downloads": -1, "filename": "bullhorn_interface-1.0.0.dev0.tar.gz", "has_sig": false, "md5_digest": "cfa2e92bf00f56b01b65da549332d78a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 12187, "upload_time": "2017-08-02T19:34:51", "url": "https://files.pythonhosted.org/packages/bc/96/69c224f3bd42adb910435925f9e825f6d7c98cb2841214a206854fa62e05/bullhorn_interface-1.0.0.dev0.tar.gz" } ], "2.0.10.dev0": [ { "comment_text": "", "digests": { "md5": "419637669d85ba183452d87cecfdc3c9", "sha256": "3df9b0a379a9f52ee611548ae38ab0129e8523c5a94896c0847c4213ca3abcf7" }, "downloads": -1, "filename": "bullhorn_interface-2.0.10.dev0.tar.gz", "has_sig": false, "md5_digest": "419637669d85ba183452d87cecfdc3c9", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 19731, "upload_time": "2017-10-18T17:50:06", "url": "https://files.pythonhosted.org/packages/be/22/80236913444ab2924900ba797d29bfd894630e0aab8a4078a6a21c88a7aa/bullhorn_interface-2.0.10.dev0.tar.gz" } ], "2.0.11.dev0": [ { "comment_text": "", "digests": { "md5": "d34c01fec41f84b64beb5702071df4dc", "sha256": "787a873baa2c9ca5b96f25262ebbbddfd094908e885b13b25ece5ecfb76858c5" }, "downloads": -1, "filename": "bullhorn_interface-2.0.11.dev0.tar.gz", "has_sig": false, "md5_digest": "d34c01fec41f84b64beb5702071df4dc", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21151, "upload_time": "2017-12-14T21:30:03", "url": "https://files.pythonhosted.org/packages/6b/a2/e5889a0a9da048d6129d13b0ff96f1065039eadc9b58b6ec037beaa80a8f/bullhorn_interface-2.0.11.dev0.tar.gz" } ], "2.0.13.dev0": [ { "comment_text": "", "digests": { "md5": "505a128672bd3c643fbaf5c154b81880", "sha256": "f9951857528f4f23e281ecece14421b5cc60aa70ee3f28625f95a349a29e5693" }, "downloads": -1, "filename": "bullhorn_interface-2.0.13.dev0.tar.gz", "has_sig": false, "md5_digest": "505a128672bd3c643fbaf5c154b81880", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21668, "upload_time": "2018-02-02T20:20:27", "url": "https://files.pythonhosted.org/packages/61/6a/0a19fc5ecb069cc4d1dab3a0dc4f44e2ad429ac9a02894e6bc722d6c4d45/bullhorn_interface-2.0.13.dev0.tar.gz" } ], "2.0.14.dev0": [ { "comment_text": "", "digests": { "md5": "eb27df5cec4e305eebb6c1b73bda4d5c", "sha256": "e0d5d820ed3005727cc050d3305623c305ede8e98074d1a4da5b011cf753be3c" }, "downloads": -1, "filename": "bullhorn_interface-2.0.14.dev0.tar.gz", "has_sig": false, "md5_digest": "eb27df5cec4e305eebb6c1b73bda4d5c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21676, "upload_time": "2018-02-05T20:39:09", "url": "https://files.pythonhosted.org/packages/0b/f6/cdd9c67469f1075386806bbb838da6019079a5a61a271911b503cdf9aa6f/bullhorn_interface-2.0.14.dev0.tar.gz" } ], "2.0.8.dev0": [ { "comment_text": "", "digests": { "md5": "2fd4f5765d99fed37467989fd2d6c267", "sha256": "eb4d2ad556bd2bd4e5f01560b0bbbf3413404cfaa3747579f0c83137c530b0dc" }, "downloads": -1, "filename": "bullhorn_interface-2.0.8.dev0.tar.gz", "has_sig": false, "md5_digest": "2fd4f5765d99fed37467989fd2d6c267", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 16335, "upload_time": "2017-10-03T17:17:28", "url": "https://files.pythonhosted.org/packages/33/98/28ac9710d601c26cf1c8f68d6732f47914ddcb7b26da2b8b1d92682490e4/bullhorn_interface-2.0.8.dev0.tar.gz" } ], "2.0.9.dev0": [ { "comment_text": "", "digests": { "md5": "34d43ff58cb197f8cb4480a61f853eb6", "sha256": "839562b20c918b121c1b4c89117efdd3d7e50b09884a978dee3c6ace0265a172" }, "downloads": -1, "filename": "bullhorn_interface-2.0.9.dev0.tar.gz", "has_sig": false, "md5_digest": "34d43ff58cb197f8cb4480a61f853eb6", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 18737, "upload_time": "2017-10-05T22:12:56", "url": "https://files.pythonhosted.org/packages/78/cc/8a87c18f9d1996709fbc926723fd39041585c01515d31df31e29b86aa7bb/bullhorn_interface-2.0.9.dev0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "eb27df5cec4e305eebb6c1b73bda4d5c", "sha256": "e0d5d820ed3005727cc050d3305623c305ede8e98074d1a4da5b011cf753be3c" }, "downloads": -1, "filename": "bullhorn_interface-2.0.14.dev0.tar.gz", "has_sig": false, "md5_digest": "eb27df5cec4e305eebb6c1b73bda4d5c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21676, "upload_time": "2018-02-05T20:39:09", "url": "https://files.pythonhosted.org/packages/0b/f6/cdd9c67469f1075386806bbb838da6019079a5a61a271911b503cdf9aa6f/bullhorn_interface-2.0.14.dev0.tar.gz" } ] }