{
"info": {
"author": "Patrick Lehmann",
"author_email": "Paebbels@gmail.com",
"bugtrack_url": null,
"classifiers": [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3 :: Only",
"Topic :: Utilities"
],
"description": "\n \n\n[](https://pyattributes.readthedocs.io/en/latest/?badge=latest) \n[](https://pypi.org/project/pyAttributes/)\n\n\n\n\n# pyAttributes\n\n**.NET-like Attributes implemented as Python decorators**\n\nI have played a bit with class-based decorators and as far as I can tell it's possible to implement .NET-like attributes in Python.\n\n## So first let's develop a meaningful use case:\nMost of us know the Python `argparse` command line argument parser. This parser can handle sub-commands like `git commit -m \"message\"` where *commit* is a sub-command and `-m ` is a parameter of this sub-command parser. It's possible to assign a callback function to each sub-command parser.\n\n> **Python 3.4.2 for Windows** has a bug in handling callback functions. It's fixed in 3.5.0 (I haven't tested other 3.4.x versions).\n\nHere is a classic `argparse` example:\n\n```python\nclass MyProg():\n\tdef Run(self):\n\t\t# create a commandline argument parser\n\t\tMainParser = argparse.ArgumentParser(\n\t\t\tdescription = textwrap.dedent('''This is the User Service Tool.'''),\n\t\t\tformatter_class = argparse.RawDescriptionHelpFormatter,\n\t\t\tadd_help=False)\n\n\t\tMainParser.add_argument('-v', '--verbose', dest=\"verbose\", help='print out detailed messages', action='store_const', const=True, default=False)\n\t\tMainParser.add_argument('-d', '--debug', dest=\"debug\", help='enable debug mode', action='store_const', const=True, default=False)\n\t\tMainParser.set_defaults(func=self.HandleDefault)\n\t\tsubParsers = MainParser.add_subparsers(help='sub-command help')\n\n\t\t# UserManagement commads\n\t\t# create the sub-parser for the \"create-user\" command\n\t\tCreateUserParser = subParsers.add_parser('create-user', help='create-user help')\n\t\tCreateUserParser.add_argument(metavar='', dest=\"Users\", type=str, nargs='+', help='todo help')\n\t\tCreateUserParser.set_defaults(func=self.HandleCreateUser)\n\n\t\t# create the sub-parser for the \"remove-user\" command\n\t\tRemoveUserParser = subParsers.add_parser('remove-user', help='remove-user help')\n\t\tRemoveUserParser.add_argument(metavar='', dest=\"UserIDs\", type=str, nargs='+', help='todo help')\n\t\tRemoveUserParser.set_defaults(func=self.HandleRemoveUser)\n\n\tdef HandleDefault(self, args):\n\t\tprint(\"HandleDefault:\")\n\n\tdef HandleCreateUser(self, args):\n\t\tprint(\"HandleCreateUser: {0}\".format(str(args.Users)))\n\n\tdef HandleRemoveUser(self, args):\n\t\tprint(\"HandleRemoveUser: {0}\".format(str(args.UserIDs)))\n\nmy = MyProg()\nmy.Run()\n```\n\nA better and more descriptive solution could look like this:\n\n```python\nclass MyProg():\n\tdef __init__(self):\n\t\tself.BuildParser()\n\t\t# ...\n\tdef BuiltParser(self):\n\t\t# 1. search self for methods (potential handlers)\n\t\t# 2. search this methods for attributes\n\t\t# 3. extract Command and Argument attributes\n\t\t# 4. create the parser with that provided metadata\n\n\t# UserManagement commads\n\t@CommandAttribute('create-user', help=\"create-user help\")\n\t@ArgumentAttribute(metavar='', dest=\"Users\", type=str, nargs='+', help='todo help')\n\tdef HandleCreateUser(self, args):\n\t\tprint(\"HandleCreateUser: {0}\".format(str(args.Users)))\n\n\t@CommandAttribute('remove-user',help=\"remove-user help\")\n\t@ArgumentAttribute(metavar='', dest=\"UserIDs\", type=str, nargs='+', help='todo help')\n\tdef HandleRemoveUser(self, args):\n\t\tprint(\"HandleRemoveUser: {0}\".format(str(args.UserIDs)))\n```\n\n-------\n\nSo let's develop a solution step-by-step.\n\n## Step 1 - A common `Attribute` class\n\nSo let's develop a common `Attribute` class, which is also a class-based decorator. This decorator adds himself to a list called `__attributes__`, which is registered on the function which is to be decorated.\n\n```python\nclass Attribute():\n\tAttributesMemberName = \"__attributes__\"\n\t_debug = False\n\n\tdef __call__(self, func):\n\t\t# inherit attributes and append myself or create a new attributes list\n\t\tif (func.__dict__.__contains__(Attribute.AttributesMemberName)):\n\t\t\tfunc.__dict__[Attribute.AttributesMemberName].append(self)\n\t\telse:\n\t\t\tfunc.__setattr__(Attribute.AttributesMemberName, [self])\n\t\treturn func\n\n\tdef __str__(self):\n\t\treturn self.__name__\n\n\t@classmethod\n\tdef GetAttributes(self, method):\n\t\tif method.__dict__.__contains__(Attribute.AttributesMemberName):\n\t\t\tattributes = method.__dict__[Attribute.AttributesMemberName]\n\t\t\tif isinstance(attributes, list):\n\t\t\t\treturn [attribute for attribute in attributes if isinstance(attribute, self)]\n\t\treturn list()\n```\n\n## Step 2 - User defined attributes\n\nNow we can create custom attributes which inherit the basic decorative functionality from `Attribute`. I'll declare 3 attributes:\n\n - **DefaultAttribute** - If no sub-command parser recognizes a command, this decorated method will be the fallback handler.\n - **CommandAttribute** - Define a sub-command and register the decorated function as a callback.\n - **ArgumentAttribute** - Add parameters to the sub-command parser.\n\n```python\nclass DefaultAttribute(Attribute):\n\t__handler = None\n\n\tdef __call__(self, func):\n\t\tself.__handler = func\n\t\treturn super().__call__(func)\n\n\t@property\n\tdef Handler(self):\n\t\treturn self.__handler\n```\n\n```python\nclass CommandAttribute(Attribute):\n\t__command = \"\"\n\t__handler = None\n\t__kwargs = None\n\n\tdef __init__(self, command, **kwargs):\n\t\tsuper().__init__()\n\t\tself.__command = command\n\t\tself.__kwargs = kwargs\n\n\tdef __call__(self, func):\n\t\tself.__handler = func\n\t\treturn super().__call__(func)\n\n\t@property\n\tdef Command(self):\n\t\treturn self.__command\n\n\t@property\n\tdef Handler(self):\n\t\treturn self.__handler\n\n\t@property\n\tdef KWArgs(self):\n\t\treturn self.__kwargs\n```\n\n```python\nclass ArgumentAttribute(Attribute):\n\t__args = None\n\t__kwargs = None\n\n\tdef __init__(self, *args, **kwargs):\n\t\tsuper().__init__()\n\t\tself.__args = args\n\t\tself.__kwargs = kwargs\n\n\t@property\n\tdef Args(self):\n\t\treturn self.__args\n\n\t@property\n\tdef KWArgs(self):\n\t\treturn self.__kwargs\n```\n\n## Step 3 - Building a helper mixin class to handle attributes on methods\n\nTo ease the work with attributes I implemented a `AttributeHelperMixin` class, that can:\n\n - retrieve all methods of a class\n - check if a method has attributes and\n - return a list of attributes on a given method.\n\n```python\nclass AttributeHelperMixin():\n\tdef GetMethods(self):\n\t\treturn {funcname: func\n\t\t\t\t\t\tfor funcname, func in self.__class__.__dict__.items()\n\t\t\t\t\t\tif hasattr(func, '__dict__')\n\t\t\t\t\t }.items()\n\n\tdef HasAttribute(self, method):\n\t\tif method.__dict__.__contains__(Attribute.AttributesMemberName):\n\t\t\tattributeList = method.__dict__[Attribute.AttributesMemberName]\n\t\t\treturn (isinstance(attributeList, list) and (len(attributeList) != 0))\n\t\telse:\n\t\t\treturn False\n\n\tdef GetAttributes(self, method):\n\t\tif method.__dict__.__contains__(Attribute.AttributesMemberName):\n\t\t\tattributeList = method.__dict__[Attribute.AttributesMemberName]\n\t\t\tif isinstance(attributeList, list):\n\t\t\t\treturn attributeList\n\t\treturn list()\n```\n\n## Step 4 - Build an application class\n\nNow it's time to build an application class that inherits from `MyBase` and from `ArgParseMixin`. I'll discuss `ArgParseMixin` later. The class has a normal constructor, which calls both base-class constructors. It also adds 2 arguments for *verbose* and *debug* to the main-parser. All callback handlers are decorated with the new Attributes.\n\n```python\nclass MyBase():\n\tdef __init__(self):\n\t\tpass\n\nclass prog(MyBase, ArgParseMixin):\n\tdef __init__(self):\n\t\timport argparse\n\t\timport textwrap\n\n\t\t# call constructor of the main interitance tree\n\t\tMyBase.__init__(self)\n\n\t\t# Call the constructor of the ArgParseMixin\n\t\tArgParseMixin.__init__(self,\n\t\t\tdescription = textwrap.dedent('''\\\n\t\t\t\tThis is the Admin Service Tool.\n\t\t\t\t'''),\n\t\t\tformatter_class = argparse.RawDescriptionHelpFormatter,\n\t\t\tadd_help=False)\n\n\t\tself.MainParser.add_argument('-v', '--verbose', dest=\"verbose\", help='print out detailed messages', action='store_const', const=True, default=False)\n\t\tself.MainParser.add_argument('-d', '--debug', dest=\"debug\", help='enable debug mode', action='store_const', const=True, default=False)\n\n\tdef Run(self):\n\t\tArgParseMixin.Run(self)\n\n\t@DefaultAttribute()\n\tdef HandleDefault(self, args):\n\t\tprint(\"DefaultHandler: verbose={0} debug={1}\".format(str(args.verbose), str(args.debug)))\n\n\t@CommandAttribute(\"create-user\", help=\"my new command\")\n\t@ArgumentAttribute(metavar='', dest=\"Users\", type=str, help='todo help')\n\tdef HandleCreateUser(self, args):\n\t\tprint(\"HandleCreateUser: {0}\".format(str(args.Users)))\n\n\t@CommandAttribute(\"remove-user\", help=\"my new command\")\n\t@ArgumentAttribute(metavar='', dest=\"UserIDs\", type=str, help='todo help')\n\tdef HandleRemoveUser(self, args):\n\t\tprint(\"HandleRemoveUser: {0}\".format(str(args.UserIDs)))\n\np = prog()\np.Run()\n```\n\n## Step 5 - The `ArgParseMixin` helper class.\n\nThis class constructs the `argparse` based parser with the provided data from attributes. The parsing process is invoked by `Run()`.\n\n```python\nclass ArgParseMixin(AttributeHelperMixin):\n\t__mainParser = None\n\t__subParser = None\n\t__subParsers = {}\n\n\tdef __init__(self, **kwargs):\n\t\tsuper().__init__()\n\n\t\t# create a commandline argument parser\n\t\timport argparse\n\t\tself.__mainParser = argparse.ArgumentParser(**kwargs)\n\t\tself.__subParser = self.__mainParser.add_subparsers(help='sub-command help')\n\n\t\tfor funcname,func in self.GetMethods():\n\t\t\tdefAttributes = DefaultAttribute.GetAttributes(func)\n\t\t\tif (len(defAttributes) != 0):\n\t\t\t\tdefAttribute = defAttributes[0]\n\t\t\t\tself.__mainParser.set_defaults(func=defAttribute.Handler)\n\t\t\t\tcontinue\n\n\t\t\tcmdAttributes = CommandAttribute.GetAttributes(func)\n\t\t\tif (len(cmdAttributes) != 0):\n\t\t\t\tcmdAttribute = cmdAttributes[0]\n\t\t\t\tsubParser = self.__subParser.add_parser(cmdAttribute.Command, **(cmdAttribute.KWArgs))\n\t\t\t\tsubParser.set_defaults(func=cmdAttribute.Handler)\n\n\t\t\t\tfor argAttribute in ArgumentAttribute.GetAttributes(func):\n\t\t\t\t\tsubParser.add_argument(*(argAttribute.Args), **(argAttribute.KWArgs))\n\n\t\t\t\tself.__subParsers[cmdAttribute.Command] = subParser\n\t\t\t\tcontinue\n\n\tdef Run(self):\n\t\t# parse command line options and process splitted arguments in callback functions\n\t\targs = self.__mainParser.parse_args()\n\t\t# because func is a function (unbound to an object), it MUST be called with self as a first parameter\n\t\targs.func(self, args)\n\n\t@property\n\tdef MainParser(self):\n\t\treturn self.__mainParser\n\n\t@property\n\tdef SubParsers(self):\n\t\treturn self.__subParsers\n```\n\n## Contributors:\n\n* [Patrick Lehmann](https://github.com/Paebbels) (Maintainer)\n\n\n## License\n\nThis library is licensed under [Apache License 2.0](LICENSE.md)\n\n-------------------------\n\nSPDX-License-Identifier: Apache-2.0\n\n\n",
"description_content_type": "text/markdown",
"docs_url": null,
"download_url": "",
"downloads": {
"last_day": -1,
"last_month": -1,
"last_week": -1
},
"home_page": "https://github.com/Paebbels/pyAttributes",
"keywords": "Python3 Decorators",
"license": "",
"maintainer": "",
"maintainer_email": "",
"name": "pyAttributes",
"package_url": "https://pypi.org/project/pyAttributes/",
"platform": "",
"project_url": "https://pypi.org/project/pyAttributes/",
"project_urls": {
"Documentation": "https://pyAttributes.readthedocs.io/en/latest/",
"Homepage": "https://github.com/Paebbels/pyAttributes",
"Issue Tracker": "https://github.com/Paebbels/pyAttributes/issues",
"Source Code": "https://github.com/Paebbels/pyAttributes"
},
"release_url": "https://pypi.org/project/pyAttributes/0.2.6/",
"requires_dist": null,
"requires_python": ">=3.5",
"summary": ".NET-like Attributes implemented as Python decorators.",
"version": "0.2.6"
},
"last_serial": 5966087,
"releases": {
"0.2.2": [
{
"comment_text": "",
"digests": {
"md5": "02a5de2d16fe096e07764123487ee634",
"sha256": "bbade02fec4acb55f74c824e95148b30cb3275c851755623b03329596ff8f9f6"
},
"downloads": -1,
"filename": "pyAttributes-0.2.2-py3-none-any.whl",
"has_sig": false,
"md5_digest": "02a5de2d16fe096e07764123487ee634",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.5",
"size": 11556,
"upload_time": "2019-10-10T20:14:13",
"url": "https://files.pythonhosted.org/packages/39/87/e851cd2163d5bbb9350c63b324704314122f2b8305bb6b78e5198e1f54da/pyAttributes-0.2.2-py3-none-any.whl"
},
{
"comment_text": "",
"digests": {
"md5": "3c22c1659f63528df99f744fb30bf56f",
"sha256": "3600dc0b248df918aa7e8a96bdc9d4702711bb6ac57eb1ae0f86af707ad3d741"
},
"downloads": -1,
"filename": "pyAttributes-0.2.2.tar.gz",
"has_sig": false,
"md5_digest": "3c22c1659f63528df99f744fb30bf56f",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.5",
"size": 7801,
"upload_time": "2019-10-10T20:14:16",
"url": "https://files.pythonhosted.org/packages/00/75/010125ae5fc324e053d977a1a853929f9d0695b7ee4cc94338eeb931d897/pyAttributes-0.2.2.tar.gz"
}
],
"0.2.3": [
{
"comment_text": "",
"digests": {
"md5": "ca37cb878e8013ad38e9dcc6247c60df",
"sha256": "4c3928eeadcc6c561815ff4dd460d0e1c1712da58277703554aab3b58025ec51"
},
"downloads": -1,
"filename": "pyAttributes-0.2.3-py3-none-any.whl",
"has_sig": false,
"md5_digest": "ca37cb878e8013ad38e9dcc6247c60df",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.5",
"size": 11593,
"upload_time": "2019-10-11T00:34:58",
"url": "https://files.pythonhosted.org/packages/af/e6/7bbf645a5ef8b67d070043bd309612a75e2738c6d0d3b95870cbd7d20f42/pyAttributes-0.2.3-py3-none-any.whl"
},
{
"comment_text": "",
"digests": {
"md5": "75fb31df9bbee1cdd98106cd3e6e19d8",
"sha256": "7fd3cc4e28bb2d7ed8b66178b020448cca99054ca2d7c60441157fbf8909c075"
},
"downloads": -1,
"filename": "pyAttributes-0.2.3.tar.gz",
"has_sig": false,
"md5_digest": "75fb31df9bbee1cdd98106cd3e6e19d8",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.5",
"size": 7828,
"upload_time": "2019-10-11T00:34:59",
"url": "https://files.pythonhosted.org/packages/28/7d/c7aa9ff2ef33731346f2cd5200560015bcb7902f0c6aaa2871a7fed25303/pyAttributes-0.2.3.tar.gz"
}
],
"0.2.4": [
{
"comment_text": "",
"digests": {
"md5": "c623187c49395659727a3f436f06436d",
"sha256": "cf185c3c25b9ebb0a53cabb0e512baa5cc4e8caf47366b7b189d7666b995ced2"
},
"downloads": -1,
"filename": "pyAttributes-0.2.4-py3-none-any.whl",
"has_sig": false,
"md5_digest": "c623187c49395659727a3f436f06436d",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.5",
"size": 11746,
"upload_time": "2019-10-12T11:38:58",
"url": "https://files.pythonhosted.org/packages/1f/6f/096d49cfdec13118e3e7799d6862cbd5d7f7317fb84c231db081c4575bd1/pyAttributes-0.2.4-py3-none-any.whl"
},
{
"comment_text": "",
"digests": {
"md5": "07b831293e469081b35e3ae59ed69bce",
"sha256": "33193bb5c917ceeb6a49f703dc9c36f58069f94a7db78d1f380a80f1d445353e"
},
"downloads": -1,
"filename": "pyAttributes-0.2.4.tar.gz",
"has_sig": false,
"md5_digest": "07b831293e469081b35e3ae59ed69bce",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.5",
"size": 8292,
"upload_time": "2019-10-12T11:39:00",
"url": "https://files.pythonhosted.org/packages/47/25/a8a558b0ae330d4f7d96b86e900d6881774a36e8c2b9d606a4c802688bea/pyAttributes-0.2.4.tar.gz"
}
],
"0.2.5": [
{
"comment_text": "",
"digests": {
"md5": "32dceb409e24a180bc3fcce52240425c",
"sha256": "f55207d081bad1a6430e39a7e7db39349711c6787b48be5bf4f0b5cfa135a666"
},
"downloads": -1,
"filename": "pyAttributes-0.2.5-py3-none-any.whl",
"has_sig": false,
"md5_digest": "32dceb409e24a180bc3fcce52240425c",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.5",
"size": 11798,
"upload_time": "2019-10-12T23:02:04",
"url": "https://files.pythonhosted.org/packages/7e/78/ed15a7510e02b1c65882da74ef46c44802ce624df22c8bcc52efa8395be0/pyAttributes-0.2.5-py3-none-any.whl"
},
{
"comment_text": "",
"digests": {
"md5": "0c66c72c96758a451724992c5eb5e148",
"sha256": "c3d538e18981c8383e682dd73d0d75996376ffa1ce564caa134623f13820823a"
},
"downloads": -1,
"filename": "pyAttributes-0.2.5.tar.gz",
"has_sig": false,
"md5_digest": "0c66c72c96758a451724992c5eb5e148",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.5",
"size": 8330,
"upload_time": "2019-10-12T23:02:06",
"url": "https://files.pythonhosted.org/packages/2c/b6/e162d4c10faadbdbac5f92d3ace13ee788099e70efa98498049bc096013b/pyAttributes-0.2.5.tar.gz"
}
],
"0.2.6": [
{
"comment_text": "",
"digests": {
"md5": "30776f52760e2c527ee3eca32847bfb4",
"sha256": "97348cf56c4ea2f891317da2e6ca05a2d9760be64f6fc8ce7cffb5222d78a4ed"
},
"downloads": -1,
"filename": "pyAttributes-0.2.6-py3-none-any.whl",
"has_sig": false,
"md5_digest": "30776f52760e2c527ee3eca32847bfb4",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.5",
"size": 11880,
"upload_time": "2019-10-13T02:23:09",
"url": "https://files.pythonhosted.org/packages/5f/19/e3513690cef74aca3eaf093c3fcb6eecd2355b3b1367548b10428c68a70e/pyAttributes-0.2.6-py3-none-any.whl"
},
{
"comment_text": "",
"digests": {
"md5": "9472d8e41cecb0ff9576da4183364643",
"sha256": "9196107f6d04f0624605ec0363a5826710a58d0592376215975c6fb26e7dab05"
},
"downloads": -1,
"filename": "pyAttributes-0.2.6.tar.gz",
"has_sig": false,
"md5_digest": "9472d8e41cecb0ff9576da4183364643",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.5",
"size": 8427,
"upload_time": "2019-10-13T02:23:11",
"url": "https://files.pythonhosted.org/packages/4b/08/35f0562cd6d9e5babc6e9d8d54abae6a5f82c099003b394f08f7c443eef1/pyAttributes-0.2.6.tar.gz"
}
]
},
"urls": [
{
"comment_text": "",
"digests": {
"md5": "30776f52760e2c527ee3eca32847bfb4",
"sha256": "97348cf56c4ea2f891317da2e6ca05a2d9760be64f6fc8ce7cffb5222d78a4ed"
},
"downloads": -1,
"filename": "pyAttributes-0.2.6-py3-none-any.whl",
"has_sig": false,
"md5_digest": "30776f52760e2c527ee3eca32847bfb4",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.5",
"size": 11880,
"upload_time": "2019-10-13T02:23:09",
"url": "https://files.pythonhosted.org/packages/5f/19/e3513690cef74aca3eaf093c3fcb6eecd2355b3b1367548b10428c68a70e/pyAttributes-0.2.6-py3-none-any.whl"
},
{
"comment_text": "",
"digests": {
"md5": "9472d8e41cecb0ff9576da4183364643",
"sha256": "9196107f6d04f0624605ec0363a5826710a58d0592376215975c6fb26e7dab05"
},
"downloads": -1,
"filename": "pyAttributes-0.2.6.tar.gz",
"has_sig": false,
"md5_digest": "9472d8e41cecb0ff9576da4183364643",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.5",
"size": 8427,
"upload_time": "2019-10-13T02:23:11",
"url": "https://files.pythonhosted.org/packages/4b/08/35f0562cd6d9e5babc6e9d8d54abae6a5f82c099003b394f08f7c443eef1/pyAttributes-0.2.6.tar.gz"
}
]
}