{ "info": { "author": "Joe Koberg (VersionOne, Inc.)", "author_email": "Joe.Koberg@versionone.com", "bugtrack_url": null, "classifiers": [], "description": "# VersionOne Python SDK #\n\nThe VersionOne Python SDK is an open-source and community supported client for the VersionOne API.\n\nAs an open-sourced and community supported project, the VersionOne Python SDK is not formally supported by VersionOne.\n\nThat said, there are a number of options for getting your questions addressed:\n\n* [StackOverflow](http://stackoverflow.com/questions/tagged/versionone): For asking questions of the VersionOne Development Community.\n* [GitHub Issues](https://github.com/versionone/VersionOne.SDK.Python/issues): For submitting issues that others may try to address.\n\nIn general, StackOverflow is your best option for getting support for the VersionOne Python SDK.\n\nThe source code for the VersionOne Python SDK is free and open-source, and we encourage you to improve it by [submitting pull requests](https://help.github.com/articles/using-pull-requests)!\n\n## Overview\n\n### Dynamic reflection of all V1 asset types:\n\n Just instantiate a V1Meta. All asset types defined on the server are available\n as attributes on the instance. The metadata is only loaded once, so you must\n create a new instance of V1Meta to pick up metadata changes. Each asset class\n comes with properties for all asset attributes and operations.\n\n```python\nfrom v1pysdk import V1Meta\n\nwith V1Meta(\n instance_url = 'http://localhost/VersionOne',\n username = 'admin',\n password = 'admin'\n ) as v1:\n\n user = v1.Member(20) # internal numeric ID\n \n print user.CreateDate, user.Name\n```\n\n### Simple access to individual assets:\n\n Asset instances are created on demand and cached so that instances with the same OID are always\n the same object. You can retrieve an instance by passing an asset ID to an asset class:\n\n s = v1.Story(1005)\n \n \n Or by providing an OID Token:\n \n s = v1.asset_from_oid('Story:1005')\n \n print s is v1.Story(1005) # True\n\n\n### Lazyily loaded values and relations:\n\n NOTE: Making requests synchronously for attribute access on each object is costly. We recommend\n using the query syntax to select, filter, aggregate, and retrieve values from related assets \n in a single HTTP transaction.\n\n Asset instances are created empty, or with query results if available. The server is\n accessed for attributes that aren't currently fetched. A basic set of attributes is fetched\n upon the first unfound attribute. \n\n \n epic = v1.Epic(324355)\n \n # No data fetched yet.\n print epic #=> Epic(324355)\n \n # Access an attribute.\n print epic.Name #=> \"Team Features\"\n \n # Now some basic data has been fetched\n print epic #=> Epic(324355).with_data({'AssetType': 'Epic',\n 'Description': \"Make features easier for new team members\", 'AssetState': '64',\n 'SecurityScope_Name': 'Projects', 'Number': 'E-01958', 'Super_Number': 'E-01902',\n 'Scope_Name': 'Projects', 'Super_Name': 'New Feature Development',\n 'Scope': [Scope(314406)], 'SecurityScope': [Scope(314406)],\n 'Super': [Epic(312659)], 'Order': '-24', 'Name': 'Team Features'})\n \n # And further non-basic data is available, but will cause a request.\n print epic.CreateDate #=> '2012-05-14T23:45:14.124'\n \n The relationship network can be traversed at will, and assets will be fetched as needed.\n \n # Freely traverse the relationship graph\n print epic.Super.Scope.Name #=> 'Products'\n \n Since the metadata is modeled as data, you can find the list of \"Basic\" attributes:\n \n basic_attr_names = list( v1.AttributeDefinition\n .where(IsBasic = \"true\")\n .select('Name')\n .Name\n )\n \n\n### Operations:\n\n Operations on assets can be initiated by calling the appropriate method on an asset instance:\n \n for story in epic.Subs:\n story.QuickSignup() \n \n The asset instance data will be invalidated upon success, and thus re-fetched on the next\n attribute access.\n\n\n### Iterating through all assets of a type\n\n The asset class is iterable to obtain all assets of that type. This is equivalent to the\n \"query\", \"select\" or \"where\" methods when given no arguments.\n \n # WARNING: Lots of HTTP requests this way.\n members = list(v1.Member) # HTTP request to get the list of members.\n print \"Members: \" + ', '.join(m.Name for m in members) # HTTP request per member to fetch the Name\n \n # A much better way, requiring a single HTTP access via the query mechanism.\n members = v1.Member.select('Name')\n print \"Members: \" + ', '.join(m.Name for m in members) # HTTP request to return list of members with Name attribute.\n\n # There is also a shortcut for pulling an attribute off all the results\n members = v1.Member.select('Name')\n print \"Members: \" + ', '.join(members.Name)\n \n \n### Queries\n\n#### Query Objects\n\n the `select()` and `where()` methods on asset instances return a query object\n upon which you can call more `.where()`'s and `.select()`'s. Iterating through\n the query object will run the query.\n \n the `.first()` method on a query object will run the query and return the first result.\n \n Query results\n\n#### Simple query syntax:\n\n Use `.where(Attr=\"value\", ...)` to introduce \"Equals\" comparisons, and\n `.select(\"Attr\", ...)` to append to the select list.\n \n Non-\"Equal\" comparisons are not supported (Use the advanced query syntax).\n\n for s in v1.Story.where(Name='Add feature X to main product\"):\n print s.Name, s.CreateDate, ', '.join([owner.Name for owner in s.Owners])\n \n # Select only some attributes to reduce traffic\n \n for s in v1.Story.select('Name', 'Owners').where(Estimate='10'):\n print s.Name, [o.Name for o in s.Owners]\n \n \n#### Advanced query, taking the standard V1 query syntax.\n\n The \"filter\" operator will take arbitrary V1 filter terms.\n\n for s in (v1.Story\n .filter(\"Estimate>'5',TotalDone.@Count<'10'\")\n .select('Name')):\n print s.Name\n\n\n#### Advanced selection, taking the standard V1 selection syntax.\n\n The \"select\" operator will allow arbitrary V1 \"select\" terms, and will add\n them to the \"data\" mapping of the result with a key identical to the term used.\n \n select_term = \"Workitems:PrimaryWorkitem[Status='Done'].Estimate.@Sum\"\n total_done = ( v1.Timebox\n .where(Name=\"Iteration 25\")\n .select(select_term)\n )\n for result in total_done:\n print \"Total 'Done' story points: \", result.data[select_term]\n \n\n#### Advanced Filtering and Selection\n\n get a list of all the stories dedicated people are working on\n\n writer = csv.writer(outfile)\n results = (\n v1.Story\n .select('Name', 'CreateDate', 'Estimate', 'Owners.Name')\n .filter(\"Owners.OwnedWorkitems.@Count='1'\")\n )\n for result in results:\n writer.writerow((result['Name'], ', '.join(result['Owners.Name'])))\n \n \n### Simple creation syntax:\n\n GOTCHA: All \"required\" attributes must be set, or the server will reject the data.\n \n from v1pysdk import V1Meta\n v1 = V1Meta(username='admin', password='admin')\n new_story = v1.Story.create(\n Name = 'New Story',\n Scope = v1.Scope.where(Name='2012 Projects').first()\n )\n # creation happens immediately. No need to commit.\n print new_story.CreateDate\n new_story.QuickSignup()\n print 'Owners: ' + ', '.join(o.Name for o in story.Owners)\n\n\n### Simple update syntax.\n\n Nothing is written until V1Meta.commit() is called, and then all dirty assets are written out.\n\n story = v1.Story.where(Name='Super Cool Feature do over').first()\n story.Name = 'Super Cool Feature Redux'\n story.Owners = v1.Member.where(Name='Joe Koberg') \n v1.commit() # flushes all pending updates to the server\n\n The V1Meta object also serves as a context manager which will commit dirty object on exit.\n \n with V1Meta() as v1:\n story = v1.Story.where(Name='New Features').first()\n story.Owners = v1.Member.where(Name='Joe Koberg')\n \n print \"Story committed implicitly.\"\n\n\n### Attachment Contents\n\n Attachment file bodies can be fetched or set with the special \"file_data\" attribute on Attachment instances. \n\n See the v1pysdk/tests/test_attachment.py file for a full example.\n\n### As Of / Historical Queries\n\n Queries can return data \"as of\" a specific point in the past. The .asof() query term can\n take a list (or multiple positional parameters) of timestamps or strings in ISO date format.\n The query is run for each timestamp in the list. A single iterable is returned that will\n iterate all of the collected results. The results will all contain a data item \"AsOf\" with\n the \"As of\" date of that item. Note that the \"As of\" date is not the date of the previous\n change to the item, but rather is exactly the same date passed into the query. Also note\n that timestamps such as \"2012-01-01\" are taken to be at the midnight starting that day, which\n naturally excludes any activity happening during that day. You may want to specify a timestamp\n with a specific hour, or of the following day.\n \n TODO: what timezone is used?\n \n with V1Meta() as v1:\n results = (v1.Story\n .select(\"Owners\")\n .where(Name=\"Fix HTML5 Bug\")\n .asof(\"2012-10-10\", \"2012-10-11\")\n )\n for result in results:\n print result.data['AsOf'], [o.Name for o in result.Owners]\n \n \n### Polling (TODO)\n\n A simple callback api will be available to hook asset changes\n \n from v1meta import V1Meta\n from v1poll import V1Poller\n \n MAILBODY = \"\"\"\n From: VersionOne Notification \n To: John Smith \n \n Please take note of the high risk story '{0}' recently created in VersionOne.\n \n Link: {1}\n \n \n Thanks,\n \n Your VersionOne Software\n \"\"\".lstrip()\n \n def notify_CTO_of_high_risk_stories(story):\n if story.Risk > 10:\n import smtplib, time\n server = smtplib.SMTP('smtp.mycorp.com')\n server.sendmail(MAILBODY.format(story.Name, story.url))\n server.quit()\n story.CustomNotificationLog = (story.CustomNotificationLog +\n \"\\n Notified CTO on {0}\".format(time.asctime()))\n \n with V1Meta() as v1:\n with V1Poller(v1) as poller:\n poller.run_on_new('Story', notify_CTO_of_high_risk_stories)\n \n print \"Notification complete and log updated.\"\n \n \n \n## Performance notes\n\n An HTTP request is made to the server the first time each asset class is referenced.\n \n Assets do not make a request until a data item is needed from them. Further attribute access\n is cached if a previous request returned that attribute. Otherwise a new request is made.\n \n The fastest way to collect and use a set of assets is to query, with the attributes\n you expect to use included in the select list. The entire result set will be returned\n in a single HTTP transaction\n \n Writing to assets does not require reading them; setting attributes and calling the commit\n function does not invoke the \"read\" pipeline. Writing assets requires one HTTP POST per dirty\n asset instance.\n \n When an asset is committed or an operation is called, the asset data is invalidated and will\n be read again on the next attribute access.\n\n## TODO\n\n * Make things Moment-aware\n \n * Convert types between client and server (right now everything is a string)\n \n * Add debug logging\n \n * Beef up test coverage\n \n * Need to mock up server\n \n * Examples\n \n * provide an actual integration example\n \n * Asset creation templates and creation \"in context of\" other asset\n \n * Correctly handle multi-valued attributes including removal of values.\n \n## Installation\n\nrun `python setup.py install`, or just copy the v1pysdk folder into your PYTHONPATH.\n\n\n## Revision History\n\n\n2013-09-27 v0.4 - A correction has been made to the multi-valued relation setter code. It used the\n wrong value for the XML \"act\" attribute, so multi-value attributes never got set correctly. Note\n that at this time, there is no way to un-set a value from a multi-valued relation.\n\n2013-07-09 v0.3 - To support HTTPS, A \"scheme\" argument has been added to the V1Meta and V1Client\n constructors.\n\n An instance_url keyword argument was added to V1Meta and V1Client. This argument can be\n specified instead of the address, instance_path, scheme, and port arguments.\n\n A performance enhancement was made to calls such as \"list(v1.Story.Name)\". The requested\n attribute is added to the select list if it's not present, thus preventing an HTTP GET\n for each matched asset.\n\n Some poor examples were removed and logging cleaned up in places.\n\n Fix some issues with NTLM and urllib2. (thanks campbellr)\n\n Missing attributes now return a None-like object can be deferenced to any depth. (thanks bazsi)\n\n\n## License ##\n\nRedistribution and use in source and binary forms, with or without \nmodification, are permitted provided that the following conditions are \nmet:\n\n* Redistributions of source code must retain the above copyright \n notice, this list of conditions and the following disclaimer.\n \n* Redistributions in binary form must reproduce the above copyright \n notice, this list of conditions and the following disclaimer in the \n documentation and/or other materials provided with the distribution.\n \n* Neither the name of VersionOne, Inc. nor the names of its \n contributors may be used to endorse or promote products derived from \n this software without specific prior written permission of \n VersionOne, Inc.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND \nCONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, \nINCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF \nMERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE ARE \nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS\nBE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, \nEXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED \nTO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, \nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON \nANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR \nTORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF \nTHE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF \nSUCH DAMAGE.\n", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "http://github.com/VersionOne/v1pysdk", "keywords": "versionone v1 api sdk", "license": "MIT/BSD", "maintainer": null, "maintainer_email": null, "name": "v1pysdk-unofficial", "package_url": "https://pypi.org/project/v1pysdk-unofficial/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/v1pysdk-unofficial/", "project_urls": { "Download": "UNKNOWN", "Homepage": "http://github.com/VersionOne/v1pysdk" }, "release_url": "https://pypi.org/project/v1pysdk-unofficial/0.4.post4/", "requires_dist": null, "requires_python": null, "summary": "VersionOne API client", "version": "0.4.post4" }, "last_serial": 3965636, "releases": { "0.4": [ { "comment_text": "", "digests": { "md5": "3c68a038ce5fbf602d8e699abba3104b", "sha256": "fe13ff4f8840b5cf6c0dca6390609baa0763711fcb54a14f64269da684cf028d" }, "downloads": -1, "filename": "v1pysdk-unofficial-0.4.tar.gz", "has_sig": false, "md5_digest": "3c68a038ce5fbf602d8e699abba3104b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 18666, "upload_time": "2014-09-26T01:55:15", "url": "https://files.pythonhosted.org/packages/92/8d/48adfcee8832c88fd8b70f68b95087f481e85c599124b009134c982198df/v1pysdk-unofficial-0.4.tar.gz" } ], "0.4.post1": [ { "comment_text": "", "digests": { "md5": "5ed6678af78f976a29c416c1e5b4efb4", "sha256": "dfbe2bea846b9ce0d58c4cc8639b79eb352d0f5d29a948d511a2867713dac8a2" }, "downloads": -1, "filename": "v1pysdk-unofficial-0.4.post1.tar.gz", "has_sig": false, "md5_digest": "5ed6678af78f976a29c416c1e5b4efb4", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 18856, "upload_time": "2014-11-21T02:07:04", "url": "https://files.pythonhosted.org/packages/58/7a/e935af0d4bb7bbf634163ebbf3c857ad17bfeeefba970c8e1318b1c85104/v1pysdk-unofficial-0.4.post1.tar.gz" } ], "0.4.post2": [ { "comment_text": "", "digests": { "md5": "6d603cf8ef303a31e1839b10d9abff08", "sha256": "b2f48384decc07c74dedb975d157707ad21b260c2ef63b0d3489335508cb9aa1" }, "downloads": -1, "filename": "v1pysdk-unofficial-0.4.post2.tar.gz", "has_sig": false, "md5_digest": "6d603cf8ef303a31e1839b10d9abff08", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 18873, "upload_time": "2015-01-23T21:19:56", "url": "https://files.pythonhosted.org/packages/ab/5a/6fb8991ba33d318c605f410bc1535c64b3d242844359bbf90db090ac2012/v1pysdk-unofficial-0.4.post2.tar.gz" } ], "0.4.post3": [ { "comment_text": "", "digests": { "md5": "86e5b750132a7e42ddb37d38657e287a", "sha256": "b81637feee9739408e905adf415b6766e02cda86d30f538b3edbbc7d24362bcf" }, "downloads": -1, "filename": "v1pysdk-unofficial-0.4.post3.tar.gz", "has_sig": false, "md5_digest": "86e5b750132a7e42ddb37d38657e287a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 18644, "upload_time": "2015-11-24T18:11:05", "url": "https://files.pythonhosted.org/packages/98/79/499f28769e2ca417522c38c8ac1c01625ad959296b2f5ae71bac554ea2d8/v1pysdk-unofficial-0.4.post3.tar.gz" } ], "0.4.post4": [ { "comment_text": "", "digests": { "md5": "422cfad358d95b88b83c19af445d2c2d", "sha256": "287d29b806ca7f5b7ab497b1bc4247fad225bd6949e54d552da03b6cd1f188a0" }, "downloads": -1, "filename": "v1pysdk-unofficial-0.4.post4.tar.gz", "has_sig": false, "md5_digest": "422cfad358d95b88b83c19af445d2c2d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 18649, "upload_time": "2016-04-01T16:30:03", "url": "https://files.pythonhosted.org/packages/c2/72/cbaace417b31c2255bb13236377189aaaa12c6f26fbbeeb39adf49393305/v1pysdk-unofficial-0.4.post4.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "422cfad358d95b88b83c19af445d2c2d", "sha256": "287d29b806ca7f5b7ab497b1bc4247fad225bd6949e54d552da03b6cd1f188a0" }, "downloads": -1, "filename": "v1pysdk-unofficial-0.4.post4.tar.gz", "has_sig": false, "md5_digest": "422cfad358d95b88b83c19af445d2c2d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 18649, "upload_time": "2016-04-01T16:30:03", "url": "https://files.pythonhosted.org/packages/c2/72/cbaace417b31c2255bb13236377189aaaa12c6f26fbbeeb39adf49393305/v1pysdk-unofficial-0.4.post4.tar.gz" } ] }