Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

updates to the release GA #513

Merged
merged 11 commits into from
Jun 18, 2024
33 changes: 32 additions & 1 deletion .github/workflows/validate_and_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,33 @@ jobs:
python scripts/jsonParser.py
reproschema validate examples

generate_other_formats:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note that this will generate the files on every PR but that they will no be added to the repo or the release

files generated on a given job are not passed the following ones in github action, so maybe better to have the file generation happen in the release job

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I didn't get the structure at the beginning, just moved

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will be testing || true

runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.12
- name: Install dependencies
run: |
python -m pip install --upgrade pip setuptools
pip install linkml astor pre-commit
pip install git+https://github.com/ReproNim/reproschema-py.git
- name: generate pydantic and fixing it
run: |
gen-pydantic --pydantic-version 2 linkml-schema/reproschema.yaml > reproschema_model_autogen.py
python scripts/fix_pydantic.py reproschema_model_autogen.py reproschema_model.py
pre-commit run --files reproschema_model.py
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the file is auto-generated we could just not lint it with pre-commit, but if we do then this step in workflow must have continue-on-error or an always

otherwise precommit will fail and stop everything: see here https://github.com/ReproNim/reproschema/actions/runs/9568185070/job/26377757321?pr=513#step:5:162

See: https://stackoverflow.com/questions/62045967/is-there-a-way-to-continue-on-error-while-still-getting-correct-feedback

I think always() is the recommended way or that continue-on-error was deprecated

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should run this at some point, since this will be later added to reproschema-py (and perhaps if we do it here it will be easier to automatically push to to reproschema-py)

- name: generate jsonld
run: |
gen-jsonld --context contexts/reproschema linkml-schema/reproschema.yaml > reproschema.jsonld
- name: generate n-triples and turtle formats using reproschema
run: |
reproschema convert --format n-triples reproschema.jsonld > reproschema.nt
reproschema convert --format turtle reproschema.jsonld > reproschema.ttl


release:
needs: [validate]
if: github.event_name == 'workflow_dispatch'
Expand All @@ -51,7 +78,11 @@ jobs:
run: |
echo "Making a release ${{ inputs.version }}"
mkdir releases/${{ inputs.version }}
cp contexts/reproschema releases/${{ inputs.version }}/base
cp contexts/reproschema releases/${{ inputs.version }}/reproschema
cp reproschema_model.py releases/${{ inputs.version }}/reproschema_model.py
cp reproschema.jsonld releases/${{ inputs.version }}/reproschema.jsonld
cp reproschema.nt releases/${{ inputs.version }}/reproschema.nt
cp reproschema.ttl releases/${{ inputs.version }}/reproschema.ttl
# python scripts/makeRelease.py ${{ inputs.version }}

- name: Open pull requests to add files
Expand Down
22 changes: 9 additions & 13 deletions linkml-schema/reproschema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ slots:
slot_uri: schema:associatedMedia
audio:
title: audio
description: TODO
description: An audio object.
slot_uri: schema:audio
any_of:
- range: uri
Expand Down Expand Up @@ -101,12 +101,8 @@ slots:
contentUrl:
slot_uri: schema:contentUrl
range: uriorcurie
creator:
slot_uri: schema:creator
range: Person
cronTable:
title: cronTable
description: TODO not described in reproschema
slot_uri: reproschema:cronTable
datumType:
title: datumType
Expand Down Expand Up @@ -264,7 +260,7 @@ slots:
range: OverrideProperty
preamble:
title: Preamble
description: The preamble for an assessment
description: The preamble for an assessment.
slot_uri: reproschema:preamble
multivalued: true
range: langString
Expand Down Expand Up @@ -326,7 +322,7 @@ slots:
range: datetime
slot_uri: prov:startedAtTime
subject_id:
slot_uri: nidm:subject_id #TODO check this @type:rdf:Property
slot_uri: nidm:subject_id
range: string
ui:
title: UI
Expand Down Expand Up @@ -502,7 +498,7 @@ classes:
class_uri: rdf:langString
MediaObject:
title: Media Object
description: Add description #TODO
description: A media object, such as an image, video, audio, or text object embedded in a web page or a downloadable dataset.
is_a: Thing
class_uri: schema:MediaObject
slots:
Expand Down Expand Up @@ -542,7 +538,7 @@ classes:
- id
- subject_id
class_uri: reproschema:Participant
Protocol: # TODO multiple types
Protocol:
title: Protocol
description: A representation of a study which comprises one or more assessments.
is_a: Thing
Expand All @@ -555,6 +551,7 @@ classes:
- description
- landingPage
- messages
- preamble
- prefLabel
- schemaVersion
- ui
Expand Down Expand Up @@ -596,7 +593,7 @@ classes:
- unitOptions
- valueType
class_uri: reproschema:ResponseOption
SoftwareAgent: # TODO multiple types
SoftwareAgent:
title: Software Agent
description:
Captures information about some action that took place. It also links to information
Expand All @@ -616,9 +613,8 @@ classes:
- category
class_uri: schema:Thing
UI:
title: todo
description:
- todo
title: UI properties
description: A group of properties related to UI.
slots:
- order
- addProperties
Expand Down
98 changes: 98 additions & 0 deletions scripts/fix_pydantic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
""" Using ast transformer to fix some issues with automatic pydantic generation"""

import sys
import ast
import astor


class ClassRemover(ast.NodeTransformer):
def __init__(self, class_name):
self.class_name = class_name

def visit_ClassDef(self, node):
# Remove the class if its name matches the class_to_remove
if node.name == self.class_name:
return None
return node

def visit_Expr(self, node):
# Check if the node is a call expression
if isinstance(node.value, ast.Call):
# Check if the call expression is an attribute (method call)
if isinstance(node.value.func, ast.Attribute):
# Check if the method call matches the specified class and method name
if (
isinstance(node.value.func.value, ast.Name)
and node.value.func.value.id == self.class_name
):
return None # Remove this node
return self.generic_visit(node)


class TypeReplacer(ast.NodeTransformer):
def __init__(self, old_type, new_type):
self.old_type = old_type
self.new_type = new_type

def visit_FunctionDef(self, node):
# Check all arguments in the function definition
for arg in node.args.args:
if arg.annotation:
arg.annotation = self.visit(arg.annotation)
return self.generic_visit(node)

def visit_AsyncFunctionDef(self, node):
# Handle async function definitions similarly
for arg in node.args.args:
if arg.annotation:
arg.annotation = self.visit(arg.annotation)
return self.generic_visit(node)

def visit_Name(self, node):
# Replace the old type with the new type
if node.id == self.old_type:
node.id = self.new_type
return node

def visit_Subscript(self, node):
# Handle Union, Optional, and other subscripted types
node.value = self.visit(node.value)
node.slice = self.visit(node.slice)
return node

def visit_Index(self, node):
# Handle the index part of subscripted types
node.value = self.visit(node.value)
return node

def visit_Tuple(self, node):
# Handle tuples in type annotations
node.elts = [self.visit(elt) for elt in node.elts]
return node


def edit_pydantic(input_file, output_file):

with open(input_file, "r") as file:
tree = ast.parse(file.read())

transformer_class = ClassRemover(class_name="LangString")
tree_modclass = transformer_class.visit(tree)

transformer_tp = TypeReplacer(old_type="LangString", new_type="Dict[str, str]")
tree_modclass_modtype = transformer_tp.visit(tree_modclass)

with open(output_file, "w") as file:
file.write(astor.to_source(tree_modclass_modtype))


if __name__ == "__main__":
input_file = sys.argv[1]
if len(sys.argv) < 3:
output_file = input_file
else:
output_file = sys.argv[2]
print(
f"Fixing automatically generated pydantic file {input_file} and saving to {output_file}"
)
edit_pydantic(input_file, output_file)
Loading