{ "info": { "author": "Tobias Kohn", "author_email": "kohnt@tobiaskohn.ch", "bugtrack_url": null, "classifiers": [ "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Programming Language :: Python :: 3" ], "description": "# pyPMatch\n\n> **This is work in progress!** Expect some rough spots, and some features to still be missing.\n\n\n_pyPMatch_ provides **Pattern Matching** in _Python_. It is mostly based on _pattern matching_ as found in \n[_Scala_](https://www.scala-lang.org/). Its main objective is to deconstruct objects, and thereby check if any\ngiven object fulfills the criteria to be deconstructed in a particular way.\n\nThis document gives a rough, unpolished overview of _pyPMatch_, and its abilities. Better documentation can be found\nin the [doc](doc)-folder, in particular the [introduction](doc/INTRODUCTION.md). There is also a FAQ further below.\n\n_pyPMatch_ requires at least Python 3.4.\n\n\n## Example\n\n_pyPMatch_ was initially developed for analysis of Python code via its _Abstract Syntax Tree_ (AST). The example below\nshows how _pyPMatch_'s pattern matching can be used to implement a very simple code optimiser. However, there is nothing\nspecial about the `ast`-module from _pyPMatch_'s point of view, and you can equally use it in combination with anything\nelse.\n\n```python\nimport ast\nfrom ast import Add, BinOp, Num\n\ndef simplify(node):\n match node:\n case BinOp(Num(x), Add(), Num(y)):\n return Num(x + y)\n case BinOp(Num(n=x), Sub(), Num(n=y)):\n return Num(x - y)\n case ast.UnaryOp(ast.USub(), x @ Num()):\n return Num(-x.n)\n case _:\n return node\n```\n\nYou will find more examples in the [examples folder](examples); just run [run_example.py](examples/run_example.py).\n\nThere is also some documentation in the [doc](doc)-folder, in particular the [introduction](doc/INTRODUCTION.md).\n\n\n## Usage\n\n#### Install pyPMatch\n\nIn order to install the _pyPMatch_ library, simple do:\n```\npip install pyPMatch\n```\n\n\n#### Compile/Execute Code Directly\n\nIf you simply want to take _pyPMatch_ on a test drive, use `pyma_exec` as shown below.\n\n```python\nfrom pmatch import pama_exec\n\nmy_code = \"\"\"\nfrom random import randint\nmatch randint(0, 19):\n case 0:\n print(\"nothing\")\n case 1 | 4 | 9 | 16:\n print(\"a square\")\n case 2 | 3 | 5 | 7 | 11 | 13 | 17 | 19:\n print(\"a prime\")\n case _:\n print(\"some other number\")\n\"\"\"\n\npama_exec(my_code)\n```\n\nIt is equally possible to retrieve, and inspect the code generated by _pyPMatch_, using the function `pama_translate`:\n```python\nfrom pmatch import pama_translate\n\nmy_code = \"\"\"\nfrom random import randint\nmatch randint(0, 19):\n case 0:\n print(\"nothing\")\n case 1 | 4 | 9 | 16:\n print(\"a square\")\n case 2 | 3 | 5 | 7 | 11 | 13 | 17 | 19:\n print(\"a prime\")\n case _:\n print(\"some other number\")\n\"\"\"\n\ncode, match_code = pama_translate(my_code)\nprint(code) # the translation of the original code\nprint(\"=\" * 80)\nprint(match_code) # additional code for the actual matching\n```\n\n\n#### Import Code From Python Modules\n\nIt is probably more convenient to install the auto import hook, so that all modules in your package/project are\ncompiled using the _pyPMatch_-compiler (if they contain a `case` statement, that is). The auto import is installed\ndirectly through the import of `enable_auto_import`.\n```python\nfrom pmatch import enable_auto_import\nfrom random import randint\n\nimport my_module\nmy_module.test_me( randint(0, 19) )\n```\nThe contents of `my_module.py` is then something like:\n```python\ndef test_me(arg):\n match arg:\n case 0:\n print(\"nothing\")\n case 1 | 4 | 9 | 16:\n print(\"a square\")\n case 2 | 3 | 5 | 7 | 11 | 13 | 17 | 19:\n print(\"a prime\")\n case int():\n print(\"some other number\")\n case _:\n print(\"please provide an integer\")\n```\n\n\n#### Decorate Functions\n\nIf you do not want _pyPMatch_ to mess with your code, you can still use the pattern matching in the form of function\ndecorators. You put the pattern as a string into the decorator. The function itself then takes the variables of the\npattern as parameters.\n\n```python\nfrom pmatch import case\n\n@case(\"17\")\ndef test_me():\n print(\"This is correct!\")\n\n@case(\"11 | 13 | 17 | 19\")\ndef test_me():\n print(\"At least, it's still a prime number\")\n\n@case(\"i @ int()\")\ndef test_me(i):\n print(\"The result\", i, \"is wrong\")\n\n@case(\"x\")\ndef test_me(x):\n print(\"Not even an integer?\", x)\n\ntest_me(sum([2, 3, 5, 7]))\n```\n\n**NB:** _Using decorators is, after all, not a particularly good idea for this library. The reason is that, in \ncontrast to pre-compiling modules, not all names can be properly resolved. You might therefore end up with some\nsurprises, or even crashes._ \n\n\n## How To Write Patterns\n\nPatterns can be expressed using the elements described below.\n\n> As mentioned above: **not everything is fully implemented and tested**, yet! In particular, there is only limited\n> support for `A + B` at the moment.\n\n- `Foo()` matches all instances of the class `Foo`;\n- `Foo(A, B, C)` deconstructs an instance of `Foo`, which must yield three values, which then must match the patterns\n `A`, `B`, and `C`, respectively;\n- `Foo(egg=A, ham=B)` matches all instances of `Foo`, where the attributes `egg`, and `ham` match the patterns\n `A` and `B`, respectively;\n- `12`, `'abc'`, `True` and other constants match a value if the value is equal to the constant;\n- `{ 'a': A, 'b': B }` matches if the value has an element `'a'`, as well as an element `'b'`, which match `A` and\n `B`, respectively. The value can be dictionary, but it does not have to be. You can also check for specific\n elements within a list, say, using `{ 2: A, 5: B }`;\n- `{'RE'}` matches if the value is a string that conforms to the regular expression given;\n- `{foo}` matches any value _V_ of type string, for which `V.isfoo()` evaluates to `True`. For instance, `{lower}` \n will match any string for which `V.islower()` is true;\n- `A | B | C` matches if at least one of the patterns `A`, `B`, `C` matches;\n- `[A, B, C, ..., D, E]` matches any sequence where the first three elements match `A`, `B`, and `C` and the last two \n elements match `D`, and `E`, respectively. This also includes Python's usual iterator unpacking, such as \n `[a, b, *c, d]`, which is interpreted as `[a, b, c @ ..., d]`;\n- `A + B` matches a string if it can be decomposed into the parts `A` and `B`. For instance, `'(' + x + ')'` matches\n any string that has some text enclosed in parentheses, and returns the middle part as `x`;\n- `x @ A` matches if the pattern `A` matches, and binds the value to the variable `x` if the entire match is \n successful;\n- `_` is a wildcard that matches everything;\n- `*_` and `...` are wildcards used in sequences, usually with the exact same meaning;\n- `x` is an abbreviation for `x @ _`, matches everything, and binds it to `x`.\n\n\nThere are some special cases, and limitations you should be aware of:\n\n- Any variable `x` can only be bound once inside a single pattern (a `case` statement). It is legal to reuse\n `x` in different `case` statements, but you cannot have something like `Foo(x, x)`. If you need to test if both\n values in `Foo` are equal, use `Foo(x, y) if x == y` instead;\n- You cannot bind anything inside an alternative. Hence, `A|(x @ B)|C` is illegal;\n- It is not possible to bind anything to the wildcard `_`. While `_` is a regular name in Python, it has special\n meaning in _pyPMatch_ patterns. Something like `_ @ A` is, however, not illegal, but equivalent to `A()`;\n- Even though the ellipsis `...` is a 'normal value' in Python, it has a special meaning in _pyPMatch_ as a wildcard;\n- If you want to make sure you have a _dictionary_ with certain keys/values, `{ ... }` will not suffice. Use the\n syntax `dict({ 'key': value, ... })` instead;\n- Instead of writing a regular expression on your own, you can use `{int}`, or `{float}` to check if a string value\n contains an `int`, or a `float`, respectively;\n- _pyPMatch_ does not look at the names involved. If a name is followed by parentheses as in `Foo()`, the name is taken\n to refer to a class/type, against which the value is tested. Otherwise, the name is a variable that will match any\n value. This means that the pattern `str` will match everything and override the variable `str` in the process,\n while `str()` will test if the value is a string;\n- There are a few exceptions to the last rule. Since name bindings are illegal in alternatives, anyway, you can write\n `A|B|C` as an abbreviation for `A()|B()|C()`. Furthermore, `x @ A` is interpreted as `x @ A()`, since it makes no\n sense to bind two distinct variables to the exact same value;\n- Since a variable cannot be of the form `a.b`, an attribute `a.b` by itself is equivalent to `a.b()`;\n- `3 | ... | 6` is an abbreviation for the sequence `3|4|5|6`. This syntax can be used with integers, and characters\n (single-character strings). Thus, you can also write `'a' | ... | 'z'`, for instance. Note, that here you need to\n write the ellipsis, and cannot use the otherwise equivalent token `*_`.\n\n\n#### Roadmap\n\n- Full support for regular expressions and string matching\n- Test suites\n- Documentation, tutorials\n\n\n## The Two Versions of the `case` Statement\n\nThere are two version of the `case` statement. You can either use `case` inside a `match` block, or as a standalone\nstatement.\n\nInside a `match` block, which is compared against the patterns is specified by `match`.\n```python\ndef foo(x):\n match x:\n case 'a' | ... | 'z':\n print(\"Lowercase letter\")\n case '0' | ... | '9':\n print(\"Digit\")\n case _:\n print(\"Something else\")\n```\nThe same could also be written without the `match`. In that case, you need to specify the value to be tested against\nthe pattern. This done using the `as` syntax. There is a difference, though. The standalone `case` statements will\nall be tested, so that we explicitly need to use `return` in order to avoid printing `\"Something else\"` for everything.\n```python\ndef foo(x):\n case x as 'a' | ... | 'z':\n print(\"Lowercase letter\")\n return\n case x as '0' | ... | '9':\n print(\"Digit\")\n return\n case x as _:\n print(\"Something else\")\n```\n\nAt the moment, you cannot put standalone `case` inside a `match` block, and, of course, you cannot use a `case` without\nspecifying the value outside a `match` block.\n\n\n## FAQ\n\n#### Can I Use _pyPMatch_ in My Project?\n\nYes, _pyPMatch_ is released under the [Apache 2.0 license](LICENSE), which should allow you to freely use _pyPMatch_ in your\nown projects. Since the project is currently under heavy development, the pattern matching might fail in unexpected\nways, though.\n\nIn order to provide this new syntax for pattern matching, _pyPMatch_ needs to translate your code before Python's own\nparser/compiler can touch it. But, the translation process is design to only modify the bare minimum of your original\nPython code. No commends are removed, no lines inserted or deleted, and no variables or functions renamed. But since\n`case` and `match` have become keywords, there is a possible incompatibility with your existing code.\n\nIn addition to `case` and `match`, _pyPMatch_ introduces two more names: `__match__`, and `__matchvalue__`, respectively.\nIt is very unlikely, though, that your program uses either of these names.\n\n\n#### Why Yet Another Pattern Matching Library/Proposal?\n\nThere have been discussions about adding a `switch` statement, or even pattern matching to _Python_ before (see, e.g.,\n[PEP 3103](https://www.python.org/dev/peps/pep-3103/)). Hence, _pyPMatch_ is not an new idea. In contrast to most\ndiscussion I am aware of so far, this project differs in that my focus is not on the exact syntax, but more on getting\nthe semantics right. And, at the end of the day, I just needed (or let's say 'strongly desired') pattern matching \nfor other projects I am working on.\n\nAs such, _pyPMatch_ shows how full pattern matching can be integrated with Python, but there is no claim whatsoever that \nthe syntax used here is the best possible alternative.\n\n\n#### Why Not Just Use Regular Expressions?\n\nRegular expressions are great if you want to match a string, say. The pattern matching we provide here, however, \nworks on general Python objects, and not on strings. It is more akin to something like `isinstance`, or `hasattr`\ntests in Python.\n\n\n#### How Do I Check If a Value Has a Certain Type?\n\nDue to Python's syntax, something like `s: str` will not work in order to specify that `s` should be of type `str`.\nWhat you would usually do in Python is something like `isinstance(value, str)`, which translates directly to:\n```python\ncase str():\n print(\"We have a string!\")\n``` \nMake sure you put the parentheses after the `str`, as these parentheses tell _pyPMatch_ that `str` is supposed to be a \nclass against which to test, and not a new name for the value.\n\n\n#### How Do I Check If a Value Has a Certain Attribute?\n\nIf you do not care about the class, or type, of an object, but only about its attributes, use the wildcard `_` as the\nclass name. The algorithm will then omit the `isinstance` check, and just test if the object's attributes fulfill the\ngiven conditions - which in this case is simply that there is an attribute `egg`, which can be anything.\n```python\ncase _(egg=_):\n print(\"We have something with an attribute 'egg'.\")\n```\nThe example above will be translated to a simple test of the form `hasattr(value, 'egg')`.\n\n\n#### Can I Nest The Match/Case Structures?\n\nBasically, yes, you can. The only real limitation here is that you cannot put a `match` directly inside another\n`match`, whereas it is no problem to put a `match` inside a case. That is to say that the following will fail:\n```python\nmatch x:\n match y:\n case z:\n```\nThe reason for this is that `match` puts the value of the expression `x` into a local variable (and has some further\nbook-keeping). The second `match` messes this book-keeping up, and replaces `x` by `y`, so that subsequent tests fail.\nOn the other hand, there is hardly any reason why a `match` inside another `match` should make sense, anyway.\n\nAt the moment, nesting is not yet fully implemented, though. As long you put the match/case structures in separate\nfunctions, there is never a problem.\n\n\n#### Is This Pattern Matching Library Efficient?\n\nThe primary objective of this library is correctness, not efficiency. Once everything runs, there is still time to\nworry about improving the performance of the library. However, there are some strong limitations to how efficient\npattern matching can be done in Python.\n\nSince the matching algorithm must analyse various objects, and classes, each time a matching is performed, there are\ncertainly limitations to the performance a pattern matching algorithm can deliver in Python. If you have something\nlike in the code snippet below, the algorithm must test, if `my_value` is an instance of `Foo`, if it has (at least)\nthe attributes `eggs` and `ham`, and if the value of the attribute `eggs` is `123`.\n```python\nmatch my_value:\n case Foo(eggs=123, ham=x):\n print(\"A Foo with 123 eggs has ham\", x)\n```\nIn statically compiled languages it is possible to test only once (during compilation) if class `Foo` has attributes\n`eggs` and `ham`. In Python, however, even the class `Foo` refers to might change, so that we need to test everything\nupon each matching attempt.\n\nAnother limitations is due to the fact _pyPMatch_ tries to minimize the amount your code needs to be changed. This means\nthat each `case` statement is treated in isolation from all others, and it is therefore not possible to factor out\ncommon parts. Again, there is certainly room for further improvement, but it is not a priority of _pyPMatch_.\n\n\n#### Will It Break My Code If I Use `case` and `match` as Variable Names?\n\nThere is, of course, always a danger that _pyPMatch_'s compiler will mis-identify one of your variables as a `match`,\nor `case` statement. However, in order to be recognised as a statement, either keyword (`case`, `match`) must be the\nfirst word on a line, and it cannot be followed by a colon, or an operator (such as an assignment). So, if you have\na function called `case`, the function call `case(...)` might be interpreted as a `case` statement, but an assignment\nlike `case = ...`, say, will not.\n\n\n#### Why Did You Use `@` for Name Bindings Instead of `:=`?\n\nPython 3.8 will introduce assignment expressions (see [PEP 572](https://www.python.org/dev/peps/pep-0572/)). It would\ntherefore be natural to use `x := A` instead of `x @ A` for name bindings.\n\nIn fact, I am happy to add full support for `:=`. At the time of writing, however, `:=` is not yet a valid token in\nPython. Using only `:=` would mean that _pyPMatch_ requires at least Python 3.8, while `@` has already become a valid \noperator in Python 3.5 [PEP 465](https://www.python.org/dev/peps/pep-0465/).\n\n\n#### Why `1 | ... | 9` Instead of the Simpler `1 ... 9`?\n\nThe entire syntax of patterns in _pyPMatch_ is based on standard Python syntax. Even though the patterns are semantically\nnonsense, they are syntactically valid. The sequence `1 ... 9`, however, is not a valid sequence in Python, and would\nissue a syntax error.\n\nThere are various reasons for wanting patterns to be valid Python syntax. One of them is that _pyPMatch_ gets away with\nmuch less parsing work on its own.\n\nApart from this issue of pragmatics, writing `1 | ... | 9` seems clearer to me, since `1 ... 9` could also mean that\nthe value has to be the sequence `1, 2, 3, ..., 9` itself. This is, however, a matter of personal taste, and thus\ndebatable.\n\n\n#### Why Are There Two Versions of `case` Statements?\n\nPattern matching does usually not only come in the form of `match` blocks. At times, we only want to deconstruct a\nsingle value. Python already supports this in part through assignments like `a, b, *c = x`. Using the standalone\nversion of `case`, you could write this in the form `case x as (a, b, *c):`. However, the `case` statement can do much\nmore than Python's assignment operator.\n\nOn the other hand, while developing the library, I wondered if it possible to give meaning to `case` even outside a\n`match` block, so as to make the entire syntax as orthogonal, and as flexible as possible.\n\nAs _pyPMatch_ is kind of a prototype, in the end, the standalone variant of `case` might not survive, and not make it into\nsubsequent versions. For the moment, it remains there to fully test its usefulness.\n\n\n#### Why is `match` Not an Expression as in Scala?\n\nWhile _Scala_'s syntax and semantics are based on expressions, _Python_'s is not. Compound statements like `while`,\n`if`, `for` etc. are, as a matter of fact, never expressions in Python, but clearly statements without proper value.\nSince both `match` and `case` statements, as implemented here, are obviously compound statements, it would feel very\nwrong for Python to try, and make them expressions.\n\n\n#### Why Do I Have to Use `case _` Instead Of `else`?\n\nThe implementation of _pyPMatch_ is focused on minimising the rewriting of any Python code, or module. It will only\ntranslate `case`, and `match`, statements where it is pretty certain that such a statement is meant in the first\nplace, leaving all your code around it untouched.\n\nIf we were to use `else`, this means that we would have to put a lot more effort in making sure that no `else` is\nreplaced where it should remain, leading to longer and more complex code. Moreover, the individual `case` statements\nin a `match` block are actually not linked, but stand as individual statements for themselves. Using `else` raises \ntherefore a few additional questions concerning the semantics, which need proper answering.\n\nSo, in short: using `else` would lead to a more brittle syntax with quite a few corner cases not covered.\n\n\n#### How About Some Proper Documentation?\n\nFirst priority is currently given to getting the library fully operational, and adding various test cases. Once that\nis complete, documentation will follow (and, after all, there is already a rather long README with lots of information,\nas well as several examples). If you have a specific question or concern, open an issue, or write to me directly.\n\n\n## Contributors\n\n- [Tobias Kohn](https://tobiaskohn.ch)\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/Tobias-Kohn/pyPMatch", "keywords": "", "license": "", "maintainer": "", "maintainer_email": "", "name": "pyPMatch", "package_url": "https://pypi.org/project/pyPMatch/", "platform": "", "project_url": "https://pypi.org/project/pyPMatch/", "project_urls": { "Homepage": "https://github.com/Tobias-Kohn/pyPMatch" }, "release_url": "https://pypi.org/project/pyPMatch/0.1.2/", "requires_dist": null, "requires_python": "", "summary": "Pattern Matching in Python", "version": "0.1.2" }, "last_serial": 4282914, "releases": { "0.1.0": [ { "comment_text": "", "digests": { "md5": "2de8c91559c3352a7ebe41bfcffe2b3c", "sha256": "fde229788f5b0cc9337107f052dd48e8e50a180d12a41c752d7fe5132718c9b6" }, "downloads": -1, "filename": "pyPMatch-0.1.0-py3-none-any.whl", "has_sig": false, "md5_digest": "2de8c91559c3352a7ebe41bfcffe2b3c", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 36415, "upload_time": "2018-09-12T12:02:58", "url": "https://files.pythonhosted.org/packages/eb/f8/af0f5c9c05e69d18ddbaac26d43aacef85d4b5428d27c7abcbae230ceb9f/pyPMatch-0.1.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "b94c81c7bfd862860e94f88928d2e233", "sha256": "96d47ab58d16025f3dada803530ed45052bd4b47beaa825c97c2c35c7570b2c1" }, "downloads": -1, "filename": "pyPMatch-0.1.0.tar.gz", "has_sig": false, "md5_digest": "b94c81c7bfd862860e94f88928d2e233", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 32644, "upload_time": "2018-09-12T12:02:59", "url": "https://files.pythonhosted.org/packages/03/94/ff3d28b328a03cd9dd8d795c0d7ce492993cde38542f18105cf93f63caa4/pyPMatch-0.1.0.tar.gz" } ], "0.1.1": [ { "comment_text": "", "digests": { "md5": "fa7c68499ef35a1026baa0902958b68f", "sha256": "c579aafe62f1b11cd17682067f28f0180765a35c39f296d0d6bb0b4fc345c239" }, "downloads": -1, "filename": "pyPMatch-0.1.1-py3-none-any.whl", "has_sig": false, "md5_digest": "fa7c68499ef35a1026baa0902958b68f", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 28381, "upload_time": "2018-09-14T13:47:34", "url": "https://files.pythonhosted.org/packages/8e/cf/ab0eaa2a7d11a31ea392256ad1ba99f04004b0aa33dbfb48b4058f8c8818/pyPMatch-0.1.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "e888e263217e3323bd1428bdfecb503a", "sha256": "0ddc2784f63f966251a1305ee60d430a515bcb62b39db92df00279d33b647bf1" }, "downloads": -1, "filename": "pyPMatch-0.1.1.tar.gz", "has_sig": false, "md5_digest": "e888e263217e3323bd1428bdfecb503a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 32926, "upload_time": "2018-09-14T13:47:37", "url": "https://files.pythonhosted.org/packages/06/77/f8df013431d223487b65552c65861453b3ec3c31da7facae29c8ce50cb3e/pyPMatch-0.1.1.tar.gz" } ], "0.1.2": [ { "comment_text": "", "digests": { "md5": "310352b9b9233d7d94153bce56a050dc", "sha256": "69b2d2a730c446ed43fcca2e3767d1cd92d40e8ca392686afb50ffc2213aa8c9" }, "downloads": -1, "filename": "pyPMatch-0.1.2-py3-none-any.whl", "has_sig": false, "md5_digest": "310352b9b9233d7d94153bce56a050dc", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 28494, "upload_time": "2018-09-18T09:47:56", "url": "https://files.pythonhosted.org/packages/ef/58/c4df3bd3db458062b3532fd32c32da639c6c561670f55418b9a5d92511a0/pyPMatch-0.1.2-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "5451583eb2ced5148f98e391e691bc02", "sha256": "4af6e5e2fae3772358135d4bcfa5a72259c7a4903b4d98afceeaf2da75d8ad87" }, "downloads": -1, "filename": "pyPMatch-0.1.2.tar.gz", "has_sig": false, "md5_digest": "5451583eb2ced5148f98e391e691bc02", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33205, "upload_time": "2018-09-18T09:47:58", "url": "https://files.pythonhosted.org/packages/03/f9/65ee99e4675d0375cbdd8f68e87bd860baf7ae8b5e345ae346929516aa0e/pyPMatch-0.1.2.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "310352b9b9233d7d94153bce56a050dc", "sha256": "69b2d2a730c446ed43fcca2e3767d1cd92d40e8ca392686afb50ffc2213aa8c9" }, "downloads": -1, "filename": "pyPMatch-0.1.2-py3-none-any.whl", "has_sig": false, "md5_digest": "310352b9b9233d7d94153bce56a050dc", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 28494, "upload_time": "2018-09-18T09:47:56", "url": "https://files.pythonhosted.org/packages/ef/58/c4df3bd3db458062b3532fd32c32da639c6c561670f55418b9a5d92511a0/pyPMatch-0.1.2-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "5451583eb2ced5148f98e391e691bc02", "sha256": "4af6e5e2fae3772358135d4bcfa5a72259c7a4903b4d98afceeaf2da75d8ad87" }, "downloads": -1, "filename": "pyPMatch-0.1.2.tar.gz", "has_sig": false, "md5_digest": "5451583eb2ced5148f98e391e691bc02", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33205, "upload_time": "2018-09-18T09:47:58", "url": "https://files.pythonhosted.org/packages/03/f9/65ee99e4675d0375cbdd8f68e87bd860baf7ae8b5e345ae346929516aa0e/pyPMatch-0.1.2.tar.gz" } ] }