{"id":14065331,"url":"https://github.com/projectshift/shift-schema","last_synced_at":"2026-02-27T11:35:16.155Z","repository":{"id":25347487,"uuid":"28775023","full_name":"projectshift/shift-schema","owner":"projectshift","description":"Validation library for python","archived":false,"fork":false,"pushed_at":"2024-06-08T12:08:01.000Z","size":244,"stargazers_count":1,"open_issues_count":10,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-09-22T21:42:50.583Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/projectshift.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-01-04T13:34:38.000Z","updated_at":"2024-06-08T12:08:05.000Z","dependencies_parsed_at":"2024-08-13T07:08:41.428Z","dependency_job_id":"4fbcb64c-9fd8-41c2-9412-78babf6a0168","html_url":"https://github.com/projectshift/shift-schema","commit_stats":{"total_commits":209,"total_committers":3,"mean_commits":69.66666666666667,"dds":0.5550239234449761,"last_synced_commit":"c598d1af5df40fae65cf3878b8f67accbcd059b7"},"previous_names":[],"tags_count":27,"template":false,"template_full_name":null,"purl":"pkg:github/projectshift/shift-schema","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/projectshift%2Fshift-schema","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/projectshift%2Fshift-schema/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/projectshift%2Fshift-schema/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/projectshift%2Fshift-schema/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/projectshift","download_url":"https://codeload.github.com/projectshift/shift-schema/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/projectshift%2Fshift-schema/sbom","scorecard":{"id":746678,"data":{"date":"2025-08-11","repo":{"name":"github.com/projectshift/shift-schema","commit":"223f31ad76d448ac1c2fe4bd548dfe6614167199"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.4,"checks":[{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.txt:0","Info: FSF or OSI recognized license: MIT License: LICENSE.txt:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":5,"reason":"5 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: PYSEC-2020-28 / GHSA-m6xf-fq7q-8743","Warn: Project is vulnerable to: PYSEC-2020-27 / GHSA-q65m-pv3f-wr5r","Warn: Project is vulnerable to: PYSEC-2020-340 / GHSA-vqhp-cxgc-6wmm","Warn: Project is vulnerable to: PYSEC-2021-865 / GHSA-vv2x-vrpj-qqpq","Warn: Project is vulnerable to: PYSEC-2022-43017 / GHSA-qwmp-2cf2-g9g6"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-22T18:58:42.985Z","repository_id":25347487,"created_at":"2025-08-22T18:58:42.985Z","updated_at":"2025-08-22T18:58:42.985Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29892172,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-27T09:48:51.284Z","status":"ssl_error","status_checked_at":"2026-02-27T09:48:43.992Z","response_time":57,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-13T07:04:26.107Z","updated_at":"2026-02-27T11:35:16.139Z","avatar_url":"https://github.com/projectshift.png","language":"Python","readme":"[![Build Status](https://api.travis-ci.org/projectshift/shift-schema.svg)](https://travis-ci.org/projectshift/shift-schema)\n[![Coverage Status](https://coveralls.io/repos/projectshift/shift-schema/badge.svg?branch=master)](https://coveralls.io/r/projectshift/shift-schema?branch=master)\n# shift-schema\n\n\nFiltering and validation library. Can filter and validate data in \nobjects and simple dictionaries with flexible schemas. \n\nMain idea: decouple filtering and validation rules from web forms into\nflexible schemas, then reuse those schemas in forms as well as apis and cli. Model validation and filtering rules should be part of the model and your domain logic, not your views or forms logic.\n\n## model:\n\nYou can use any kind of object or a dictionary as your model.\nIf you use filtering model will be changed in-place by applying \nthe filters you define. \n\n## schema:\n\n\nSchema is a collection of rules to filter and validate properties of your\nmodel (object or dictionary). There are several ways to create a schema\nmost nice being by subclassing `Schema` object:\n\n\n```python\nfrom shiftschema.schema import Schema\nfrom shiftschema import validators as validator\n\nclass MySchema(Schema):\n    def schema(self):\n        self.add_property('name', required=True)\n        self.name.add_validator(validator.Length(min=3, max=100))\n        \n        self.add_property('email')\n        self.email.add_validator(validator.Email)\n\nschema = MySchema()\n```\n\n\n\n## validation:\n\nYou can then use this schema to filter and validate your model data, or `process` it (filter and validate as single operation).\nTo validate a model pass it to your schema and get back `Result`:\n\n```python\nmodel = dict(name=None, email='BAD')\nvalid = schema.process(model)\nprint(valid == True) # False - validaation failed\nprint(valid.errors) # errors: name='Required', email='Invalid'\n```\n\nThere is a number of common validators provided and you can easily plug your own.\n\n## filtering:\n\nYou can attach filters to your schema. Those will be applied in turn and update model data in-place before doing any validations.\n\n```\nperson = Person(name='   Morty   ', birthyear = 'born in 1900')\nschema = Schema({\n    'properties': {\n        'name': dict(\n            filters: [Strip()]\n        ),\n        'birthyear': dict(\n            filters: [Digits(to_int=True)]\n        )\n    }\n})\n\nprint(person.name) # 'Morty' (stripped of spaces)\nprint(person.birthyar) # 1900 (int)\n```\n\nAs with validators there are some filters provided and you can easily plug your own.\n\n## errors are objects:\n\nValidation on a model gets you a `Result` objects that evaluates to boolean\n`True` or `False` depending on if it was valid or not:\n\n```python\nvalid = schema.validate(model) # return shiftschema.result.Result\nbool(valid) # True if valid, False otherwise\n```\n\nAll the errors the result contains are `Error` objects that \nsimple validators (like `Length`)return. You can easily get those errors as string \nmessages with:\n\n```python\nerrors_dict = result.get_messages()\n```\n\nAll errors are translated, so you can have them in any language supported\nby passing a locale (defaults to 'en'):\n\n```python\nerrors_dict = result.get_messages(locale='en') # translate to locale\n```\n\n## translation:\n\nYou can pass `translator` and `locale` to `Result` object manually but\nthe most easy way to use translations is through globally available\n`Translator` that exists on the `Schema`. Schemas will inject this translator\nand locale in to result objects they create. So you can simply:\n\n```python\nfrom shiftschema.schema import Schema\n\nSchema.translator # preconfigured with default translations\nSchema.translator.add_.location(path) # but you can add your own\nSchema.locale # is 'en' by default\nSchema.locale = 'ru' # but you can change that\n```\n\nAfter setting those you will get errors by default in Russian with\nyour custom translations loaded.\n\n```python\nvalid = schema.validate(model)\nif not valid:\n    valid.get_messages() # in russian per global setting\n    valid.get_messages(locale='en') # or whatever you specify\n```\n\n## provided filters:\n\nThere is a number of implemented filters already and we are constantly adding more. You also can implement your own by extending from `AbstractFilter` class. Currently the follwing filters are provided:\n\n##### Bleach\nSanitizes HTML input with [bleach](https://bleach.readthedocs.io) Usefull when accepting rich-html input from users.\n\n##### Digits\nRemoves everything from the string leaving just the digits and optionally converts result to integer.\n\n##### Linkify\nUses [bleach](https://bleach.readthedocs.io) tp parse input text for something that looks like a URL or email and add links to these elements.\n\n##### Lowercase\nConverts incoming string to lowercase. If incoming data is not a string it will be converted to one implicitly.\n\n##### Strip\nRemoves spaces, newlines or any specified characters either from fron, back or both sides of a string.\n\n##### Uppercase\nConverts incoming string to uppercase. If incoming data is not a string it will be converted to one implicitly.\n\n\n## provided validators:\n\n##### Choice\nChecks if provided value exists in an iterable of valid choices provided to constructor.\n\n##### Digits\nValidates that passed value consists only of digits.\n\n##### Email\nValidates that passed in value is a valid email. The check is regex only so we don't do any deep MX checks here\n\n##### Length\nValidates an input for being proper length. You can check for minimum length, maximum length or both.\n\n##### MultiChoice\nAccepts an interable and ensures every item in it is allowed. Just like choice, but for multiple values.\n\n##### Not empty\nAccepts an interable and ensures it is not empty\n\n##### Required\nThis is used to mark a property as required. Has modifiers to allow values like `False` or `0`.\n\n## flask wtforms extension:\n\nExtension allows you to use schemas to validate wftforms in flask applications. Forms can represent full model data or just a smaller subset of your model. Both filtering and validation will be applied to form data according to rules defined in schema.\nAnd you can mix wtf validation with schema validation. Here is a usage typical pattern:\n\n```python\nfrom shiftschema.ext.flask_wtf import Form\nfrom shiftschema.schema import Schema\nfrom shiftschema import validators, filters\nfrom wtforms import StringField, PasswordField\n\n# extend your form from provided mixin\nclass UserForm(Form):\n    \"\"\" User form used for creating new users \"\"\"\n    username = StringField('username')\n    email = StringField('email')\n    password = PasswordField('password')\n    \n# define a schema\nclass UserSchema(Schema):\n    \"\"\" User model filtering and validation schema\"\"\"\n    def schema(self):\n        self.add_property('username', required=True)\n        self.username.add_filter(filters.Strip())\n        self.username.add_validator(validators.Length(min=3, max=200))\n\n        self.add_property('email', required=True)\n        self.email.add_filter(filters.Strip())\n        self.email.add_validator(validators.Length(min=3, max=200))\n        self.email.add_validator(validators.Email())\n\n        self.add_property('password', required=True)\n        self.password.add_validator(validators.Length(min=3, max=200))\t\t\n```\n\nYou can now use schema to validate and filter form data in your views. The usage is similar to regular forms and workflow:\n\n```python\n@app.route('/register/', methods=['GET', 'POST'])\ndef register_view(self):\n    # just remember to tell the form what schema to use\n    form = UserForm(schema=UserSchema())\n    if form.validate_on_submit():\n        user = User(\n            username=form.username.data, \n            email=form.email.data, \n            password=form.password.data\n        )\n        user_service.save(user)\n        return redirect(url_for('thank.you'))\n        \n    return render_template('user/register.html')\n```\n\n\n\n\n\n\n\n\n","funding_links":[],"categories":["Python"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprojectshift%2Fshift-schema","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fprojectshift%2Fshift-schema","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprojectshift%2Fshift-schema/lists"}